feat: retire legacy HWLAB G14 runtime

This commit is contained in:
Codex
2026-06-08 17:57:41 +00:00
parent 799f35de06
commit 0920d39c7c
6 changed files with 487 additions and 80 deletions
+13 -13
View File
@@ -103,22 +103,22 @@ UniDesk 是一个以主 server 为统一入口的分布式工作平台;本文
## Critical G14 HWLAB Workspace Rule
- P0: `G14:HWLAB` 是当前 HWLAB DEV/PROD source workspace 和 k3s/GitOps 运行面真相;唯一长期 source workspace 是 G14 节点上的 `/root/hwlab`,固定使用 `G14` 分支和 `origin git@github.com:pikasTech/HWLAB.git`;所有 HWLAB 新开发、文档、render、polling、CI/CD/GitOps 修复默认都必须以该目录和 G14 运行面为准
- P0: HWLAB `v0.2` 是在 G14 上的加法扩容线,不改变、删除或重命名现有 `G14`/`G14-gitops` 分支、`hwlab-dev`/`hwlab-prod` namespace 或 17666/17667、18666/18667 入口;`v0.2` 固定使用分支 `v0.2`、开发 workspace `G14:/root/hwlab-v02`、CI/CD 专用 source repo `G14:/root/hwlab-v02-cicd.git`、namespace `hwlab-v02`、19666/19667 FRP 入口`deploy/deploy.yaml` 单一人写配置源,长期细则见 `docs/reference/g14.md`
- P0: 每次开始 `G14:HWLAB` 分布式开发、切换任务、恢复中断或上下文压缩后,必须重新读取目标 workspace 的 `/root/hwlab/AGENTS.md`,并以该文件和其引用的 HWLAB repo 内规则为当前任务约束;禁止只凭压缩摘要或主 server 记忆继续改代码。
- P0: HWLAB legacy G14 DEV/PROD 运行面已退役;`G14`/`G14-gitops``hwlab-dev`/`hwlab-prod`、17666/17667 和 18666/18667 不再作为当前开发、发布、验收或回归真相。退役状态、计划和执行入口固定为 `bun scripts/cli.ts hwlab g14 retirement status|plan|execute --confirm`,且该入口只允许触碰 legacy Argo Application 与 legacy namespace,必须保护 `hwlab-v02`/`hwlab-v03`
- P0: HWLAB 当前 G14 运行面默认收敛到 runtime lane`v0.2` 固定使用分支 `v0.2`、开发 workspace `G14:/root/hwlab-v02`、CI/CD 专用 source repo `G14:/root/hwlab-v02-cicd.git`、namespace `hwlab-v02`、19666/19667 FRP 入口`v0.3+` 通过 `hwlab nodes --node G14 --lane vNN``config/hwlab-node-lanes.yaml` 管理,长期细则见 `docs/reference/g14.md`
- P0: 每次开始 G14 HWLAB runtime lane 分布式开发、切换任务、恢复中断或上下文压缩后,必须重新读取目标 lane workspace 的 `AGENTS.md`,并以该文件和其引用的 HWLAB repo 内规则为当前任务约束;禁止只凭压缩摘要或主 server 记忆继续改代码。`/root/hwlab` 只在显式 legacy/retirement follow-up 时使用。
- 操作入口必须通过 UniDesk SSH 维护桥:host/source 操作用 `trans G14 script` 或 workspace route 加 `apply-patch`,远端文本 patch 默认使用 `apply-patch` 的 v2 引擎、旧 helper 仅通过 `apply-patch-v1` 显式调用;k3s 操作用 `trans G14:k3s ...`;禁止使用 `ssh G14 k3s ...`,定位必须写在第一个 route token,后续 token 才是 operation。
- P0: 每次开始 `G14:HWLAB` 工作前,固定仓库 `/root/hwlab` 必须先即时快进到 `origin/G14` 最新;先通过 SSH 桥执行 `cd /root/hwlab && git fetch origin G14 && git pull --ff-only origin G14 && git status --short --branch && git remote -v`。若有未提交并行变更阻碍快进,必须先判断是否能与最新 `origin/G14` 快速合并;能合并则立即合并,禁止默认 stash、丢弃或绕到落后 worktree;只有确实无法自动合并时才隔离保存并停止交给人工决策。禁止用落后的固定仓库继续预检、创建 worktree、开发、render、polling 或部署。
- G14 HWLAB service/runtime 开发必须先以固定 repo 做预检,再按目标 lane 创建独立 worktree/PRv0.2 CaseRun、短连接 CLI、trace、docs/reference 和配置治理这类无服务任务按 `docs/reference/g14.md``direct-lightweight` 规则可在 `G14:/root/hwlab-v02` 直接提交推送,不触发 PR/CI/CD/rollout。
- P0: 每次开始 G14 HWLAB runtime lane 工作前,目标固定仓库必须先即时快进到目标 remote 分支最新;例如 v0.2 使用 `trans G14:/root/hwlab-v02 script -- 'git fetch origin v0.2 && git pull --ff-only origin v0.2 && git status --short --branch && git remote -v'`。若有未提交并行变更阻碍快进,必须先判断是否能与最新 remote 快速合并;能合并则立即合并,禁止默认 stash、丢弃或绕到落后 worktree;只有确实无法自动合并时才隔离保存并停止交给人工决策。禁止用落后的固定仓库继续预检、创建 worktree、开发、render、polling 或部署。
- G14 HWLAB service/runtime 开发必须先以目标 runtime lane 固定 repo 做预检,再按目标 lane 创建独立 worktree/PRv0.2 CaseRun、短连接 CLI、trace、docs/reference 和配置治理这类无服务任务按 `docs/reference/g14.md``direct-lightweight` 规则可在 `G14:/root/hwlab-v02` 直接提交推送,不触发 PR/CI/CD/rollout。
- P0: G14 已有节点本地 GitHub/Google/DockerHub/npm 等 bootstrap 加速 proxy;在 G14 host、临时 pod、构建 pod 或透传 pod 里拉 GitHub/Google 资源前必须优先使用该 proxy,避免把网络直连失败当作代码阻塞。主入口:HTTP/HTTPS `http://127.0.0.1:10808`、SOCKS `socks5h://127.0.0.1:10808`,备份入口和 NO_PROXY 规则见 `docs/reference/g14.md`
- 禁止把 master server、D601、`/root/HWLAB``/home/ubuntu/hwlab``/workspace/hwlab` 或临时 clone 当作 `G14:HWLAB` source truthmaster server 也不得运行 HWLAB check、Playwright/browser smoke、镜像构建或其他重型验证,验证必须走 G14 `/root/hwlab`、G14 k3s/Tekton 或其他获批外部执行面。
- 禁止把 master server、D601、`/root/HWLAB``/home/ubuntu/hwlab``/workspace/hwlab`、retired `/root/hwlab` 或临时 clone 当作当前 G14 runtime lane source truthmaster server 也不得运行 HWLAB check、Playwright/browser smoke、镜像构建或其他重型验证,验证必须走目标 runtime lane workspace、G14 k3s/Tekton 或其他获批外部执行面。
- 长期细节见 `docs/reference/g14.md`G14 节点本地也保留 `/root/docs/hwlab-g14-workspace.md`,两处口径必须保持一致。
## Critical D601 HWLAB Legacy And Hardware Bridge Rule
- P0: `D601:HWLAB` 不再是 HWLAB 默认开发主阵地或运行面真相;`/home/ubuntu/workspace/hwlab-dev` 只作为历史迁移、D601 legacy 回滚、显式指定的 D601 补丁收敛和故障对照 workspace 使用。
- P0: 新的 HWLAB 代码、文档、render、polling、CI/CD、GitOps、DEV/PROD runtime 和验收默认必须走 `G14:HWLAB`;只有用户明确指定 D601 legacy 或 D601 Windows 硬件桥接时,才进入 D601 HWLAB 路径。
- D601 仍是 ConStart/71-FREQ 等 Windows 硬件与 Keil/串口资源的 bridge host;最小 device-agent 实验中,D601 Windows `hwlab-gateway` 可以作为外部 gateway 连接到 G14 `hwlab-dev` cloud-api,而不改变 HWLAB source/runtime truth。
- 每次确需进入 D601 legacy HWLAB workspace 前,仍必须通过 UniDesk SSH 桥执行 `cd /home/ubuntu/workspace/hwlab-dev && git status --short --branch && git remote -v`,并重新读取 `/home/ubuntu/workspace/hwlab-dev/AGENTS.md`;发现 D601 口径与 G14 `/root/hwlab/AGENTS.md` 冲突时,以 G14 当前规则为准。
- P0: 新的 HWLAB 代码、文档、render、polling、CI/CD、GitOps、runtime lane 和验收默认必须走 G14 runtime lane;只有用户明确指定 D601 legacy 或 D601 Windows 硬件桥接时,才进入 D601 HWLAB 路径。
- D601 仍是 ConStart/71-FREQ 等 Windows 硬件与 Keil/串口资源的 bridge host;最小 device-agent 实验中,D601 Windows `hwlab-gateway` 可以作为外部 gateway 连接到当前 G14 runtime lane cloud-api,而不改变 HWLAB source/runtime truth。
- 每次确需进入 D601 legacy HWLAB workspace 前,仍必须通过 UniDesk SSH 桥执行 `cd /home/ubuntu/workspace/hwlab-dev && git status --short --branch && git remote -v`,并重新读取 `/home/ubuntu/workspace/hwlab-dev/AGENTS.md`;发现 D601 口径与当前 G14 runtime lane 规则冲突时,以 G14 runtime lane 规则为准。
- `/home/ubuntu/workspace/hwlab``/tmp/hwlab-*``/home/ubuntu/workspace/hwlab-*` 一次性目录、`/home/ubuntu/hwlab` runner 历史目录和 master-server checkout 都不能作为 HWLAB 当前开发真相。
- 长期细节见 `docs/reference/hwlab.md`
@@ -160,7 +160,7 @@ UniDesk 是一个以主 server 为统一入口的分布式工作平台;本文
- Master server 是生产入口和控制面,不得当作构建机使用;严厉禁止在 master server 上执行 Docker 镜像构建、`docker build``docker buildx build``docker compose build``docker compose up --build`、Rust 编译/测试(`cargo build``cargo test``rustc`)、Go 编译/测试(`go build``go test`)以及其他高 CPU/高内存编译构建任务。这类任务可能拖垮生产服务器,造成 UniDesk 整体不可用。
- P0: backend-core 主 server 上线是唯一受控例外:当用户或 issue 明确要求把当前 backend-core 修复上线到 master server 时,可以在 master server 上用 `CARGO_BUILD_JOBS=1``--jobs 1` 或 CLI 内置等价限流执行 backend-core 专属 Rust 编译或 `server rebuild backend-core`;该例外不得扩展为仓库级 `check``cargo test`、Go/前端/其他服务构建或常规迭代路径,完成后必须用异步 job/status/health 证据回写 issue。
- Master server 资源也不足以承载仓库级 `check`/`test`/smoke 验证;禁止在 master server 上运行 `bun scripts/cli.ts check``node --test``node web/hwlab-cloud-web/scripts/check.mjs`、Playwright/browser smoke 或其他会遍历大仓库、启动浏览器、长时间占用 CPU/内存的校验命令。HWLAB 正式验证必须改在 G14 `/root/hwlab`、G14 k3s/Tekton 或其他获批外部执行面完成;非 HWLAB 的 D601 服务仍按各自 reference 使用 D601 原生 k3s/CI runner。
- Master server 资源也不足以承载仓库级 `check`/`test`/smoke 验证;禁止在 master server 上运行 `bun scripts/cli.ts check``node --test``node web/hwlab-cloud-web/scripts/check.mjs`、Playwright/browser smoke 或其他会遍历大仓库、启动浏览器、长时间占用 CPU/内存的校验命令。HWLAB 正式验证必须改在目标 G14 runtime lane workspace、G14 k3s/Tekton 或其他获批外部执行面完成;非 HWLAB 的 D601 服务仍按各自 reference 使用 D601 原生 k3s/CI runner。
- 镜像和编译产物必须通过标准 CI/CD 在外部构建资源上生成;HWLAB 默认使用 G14 k3s/Tekton/GitOps 或经过批准的外部 builder,非 HWLAB 服务按各自 reference 使用 D601 原生 k3s/Tekton 或其他获批外部 builder;源码以 Git remote 为 source of truthCI 产出 commit-pinned image/artifact。
- Prod 发布必须走 CD pull-only 流程:由 `deploy.json` 声明期望服务版本,生产侧 CD 拉取已经构建好的镜像或 artifact 并按 `deploy.json` 部署;禁止把 master server 本地 Docker image、临时容器层或本地编译产物作为部署真相。
- 在 master server 上只允许轻量源码编辑、Git 操作、状态/健康/日志/诊断、JSON CLI 控制面操作和受控 CD 观察;除 backend-core 主 server 上线受控例外外,一旦步骤需要构建镜像或编译 Rust/Go 等重型产物,必须停止在 master server 执行并改走 CI/CD 或 D601 构建路径。
@@ -218,9 +218,9 @@ UniDesk 是一个以主 server 为统一入口的分布式工作平台;本文
- `bun scripts/cli.ts auth-broker contract|health --dry-run|credential-request --dry-run|pr-preflight --dry-run`:查看 Auth Broker P0 Rust skeleton 与 CLI adapter contractrunner 无 `GH_TOKEN`/`GITHUB_TOKEN` 时返回结构化 `auth-missing`/`broker-needed`,不读取或打印 token 值,规则见 `docs/reference/auth-broker.md`
- `bun scripts/cli.ts gh preflight|auth status|issue ...|pr list|files|diff --stat|read|view|preflight|closeout|create|edit|update|comment|merge` / `bun scripts/code-queue-pr-preflight-example.ts`:通过 REST 执行安全 GitHub issue 读写、分页 issue list、inactive issue stale-close、脱敏 auth/status 诊断、heredoc/stdin Markdown 写入、当日滚动简报时间线 ClaudeQQ 通知、escape 扫描、只读 cleanup-plan 和 #20 board-audit、PR changed-file/stat summary、PR 创建/评论 dry-run、REST-only 低噪声 PR title/body 编辑、PR 收口元数据观察(含 merged/closed 区分与 merge commit)、低噪声 PR 收口 preflight、guarded PR merge 与 runner PR preflight`gh issue/pr read|view` 支持 `owner/repo#number` shorthand`--raw|--full` 是显式完整披露别名,`gh pr diff` 仅支持 `--stat` 紧凑 JSON`gh pr merge` 会先执行 closeout 预检并拒绝非 open、draft、冲突、非 CLEAN、失败或 pending checks 的 PR,规则见 `docs/reference/cli.md``docs/reference/code-queue-supervision.md`
- `bun scripts/cli.ts commander contract|plan --dry-run|smoke --dry-run|approval request --dry-run|prompt-lint --kind gpt55-pr`:查看 host Codex 指挥官直管微服务 skeleton 的 source/contract、无 daemon smoke 验证计划、.state/commander/ 状态模型、trace summary 聚合、ClaudeQQ 高风险请示草案和 GPT-5.5 PR prompt 边界辅助 lint;当前只返回 dry-run 计划和 backend-core `microservice proxy claudeqq` 授权后候选命令,不接 live bridge、不接管人工指挥官,不发送消息,`prompt-lint` 不作为业务 PR 门禁也不改变 `codex submit` 默认行为,规则见 `docs/reference/host-codex-commander.md`
- `bun scripts/cli.ts hwlab g14 monitor-prs [--lane g14|v02]`:一行启动异步监控 HWLAB PR;默认 base=G14 合并后滚动 G14 DEV 并写每日简报,`--lane v02` 监控 base=v0.2CI/preflight 通过后自动合并、触发 v0.2 CD,并在 PR 下评论 pending/冲突/成功/失败/超时状态,规则见 `docs/reference/g14.md``docs/reference/cli.md`
- `bun scripts/cli.ts hwlab g14 retirement status|plan|execute --confirm`:受控退役 legacy G14 DEV/PROD Argo Application 和 namespace,并写本地退役 marker 记录执行证据;base=`G14` monitor 按退役合同固定阻止重启,`bun scripts/cli.ts hwlab g14 monitor-prs --lane v02` 是当前 v0.2 PR 自动 CI/CD 入口,规则见 `docs/reference/g14.md``docs/reference/cli.md`
- `bun scripts/cli.ts agentrun v01 control-plane status|trigger-current|refresh|cleanup-runs|cleanup-released-pvs [--dry-run|--confirm]`:通过 G14 route 只读观察、手动触发、刷新 Argo 或清理 AgentRun `v0.1` completed CI workspace retention,规则见 `docs/reference/agentrun.md``docs/reference/gc.md``docs/reference/cli.md`
- `bun scripts/cli.ts hwlab cd audit --env dev` / `status|preflight|apply --dry-run`:旧 D601 HWLAB DEV CD 指挥侧 wrapper,仅用于显式 legacy 诊断和迁移对照;当前 HWLAB DEV/PROD source/runtime truth 已迁到 G14 `/root/hwlab` 与 G14 k3s/GitOps,规则见 `docs/reference/hwlab.md`
- `bun scripts/cli.ts hwlab cd audit --env dev` / `status|preflight|apply --dry-run`:旧 D601 HWLAB DEV CD 指挥侧 wrapper,仅用于显式 legacy 诊断和迁移对照;当前 HWLAB runtime truth 已迁到 G14 runtime lane,规则见 `docs/reference/hwlab.md`
- `bun scripts/cli.ts ci install/status/run/publish-backend-core/publish-user-service/run-dev-e2e/logs`:在 D601 原生 k3s 上安装和运行 Tekton CI,支持每 commit 检查、Code Queue 只读性能门禁、`CI.json` catalog 驱动的 backend-core 与 user-service commit-pinned 镜像发布和手动触发的 `origin/master:deploy.json#environments.dev` 临时 namespace e2ecatalog/producer/consumer 分工见 `docs/reference/cicd-standardization.md``run-dev-e2e` 的 Git 控制 runner、短 launcher 和 no-CD 边界见 `docs/reference/dev-ci-runner.md`Tekton 规则见 `docs/reference/ci.md`
- `bun scripts/cli.ts codex deploy <commitId>`:旧 Code Queue 兼容部署入口已禁用,原因是它会绕过受控部署边界直连 D601 部署 Code Queue;规则见 `docs/reference/codex-deploy.md`
- `bun scripts/cli.ts codex prompt-lint [prompt|--prompt-file path|--prompt-stdin]` / `codex submit [prompt] [--prompt-file path|--prompt-stdin] [--queue <id>]` / `codex execution-plane [--full|--raw]` / `codex pr-preflight [--remote]``prompt-lint` 在派发/steer 前 dry-run 检查 runner prompt 的 DEV 测试授权分级(`read-only`/`live-read`/`live-mutating`)且不回显 prompt`submit --dry-run` 同时给出 MiniMax/GPT/人工路由建议、该 lint 结果和 requested/effective execution mode;真实提交成功只返回写入确认、task id、服务级 runnerPermissions 和后续查看命令,不回显 prompt;`execution-plane` 通过 `trans D601:k3s` 只读观察 D601 原生 k3s 正式 Code Queue 执行面、旧 Compose 残留、commit/digest/worktree drift`pr-preflight` 只读检查 D601 scheduler/runner 的 GitHub token、egress 和 PR 能力,PR 型派单前必须使用,规则见 `docs/reference/cli.md``docs/reference/code-queue-supervision.md`
@@ -255,7 +255,7 @@ UniDesk 是一个以主 server 为统一入口的分布式工作平台;本文
- `docs/reference/secretary-reference.md`:秘书日程管理、时间盒、短期待办捕获和 Todo Note / Decision Center 分流规则。
- `docs/reference/code-queue-supervision.md`:Code Queue 居中调度、并发队列拆分、运行中监控、基础设施缺陷分流和验收收口规则。
- `docs/reference/hwlab.md`HWLAB 指挥侧固定 workspace、G14 主运行面、D601 legacy/硬件桥接边界、最小 device-agent/gateway 桥接模型和受控发布边界。
- `docs/reference/g14.md`G14 provider 节点、k3s 控制桥、当前 HWLAB DEV/PROD source/runtime truth、device-agent 手动实验边界、Code Queue/CI 候选目标和节点本地 VPN proxy bootstrap 边界。
- `docs/reference/g14.md`G14 provider 节点、k3s 控制桥、legacy DEV/PROD 退役边界、当前 HWLAB runtime lane、device-agent 手动实验边界、Code Queue/CI 候选目标和节点本地 VPN proxy bootstrap 边界。
- `docs/reference/g14-observability-infra.md`G14 原生 k3s 上 Prometheus Operator、`devops-infra` 监控基础设施、跨 namespace scrape 声明和安全边界。
- `docs/reference/gc.md`UniDesk 主 server 和 provider 磁盘 GC、G14/HWLAB registry retention、safe-stop 线和长期防膨胀收益规则。
- `docs/reference/observability.md`:服务日志、任务活性、通用性能指标 API 和性能面板的可观测性规则。
+2 -2
View File
@@ -10,7 +10,7 @@ CLI 可以从 `master` 快速演进,但必须兼容 `deploy.json` 固定的 CI
## CI/CD Control Boundary
CI/CD、GitOps、rollout、artifact 发布、PR 合并后的 DEV/PROD 滚动、PipelineRun 重跑/清理、Argo refresh运行面 retention 都必须由 UniDesk CLI 的高层子命令控制。稳定入口包括 `gh pr ...``hwlab g14 monitor-prs``agentrun v01 control-plane ...``deploy check|plan|apply``ci install|status|run|publish-*|logs``artifact-registry ...``server rebuild ...``dev-env ...` 和后续为特定运行面补充的同级命令。原生 `kubectl``argo``tkn``gh``curl` 或临时 shell 可以作为实现细节存在于 CLI 内部,但不能作为人工或 runner 的正式控制面。
CI/CD、GitOps、rollout、artifact 发布、PR 合并后的 runtime lane 滚动、PipelineRun 重跑/清理、Argo refresh运行面 retention 和 legacy runtime 退役都必须由 UniDesk CLI 的高层子命令控制。稳定入口包括 `gh pr ...``hwlab g14 retirement ...``hwlab g14 monitor-prs --lane v02``agentrun v01 control-plane ...``deploy check|plan|apply``ci install|status|run|publish-*|logs``artifact-registry ...``server rebuild ...``dev-env ...` 和后续为特定运行面补充的同级命令。原生 `kubectl``argo``tkn``gh``curl` 或临时 shell 可以作为实现细节存在于 CLI 内部,但不能作为人工或 runner 的正式控制面。
`trans <route> kubectl|logs|get|describe` / `tran <route> ...` 仍是 CLI 介导的低层诊断底座,用于短查询、日志尾部、只读证据和一次性故障定位。它不应承载可重复的 CI/CD 写操作:创建/删除 PipelineRun、patch Pipeline/CronJob/RBAC、annotate Argo Application、触发/回滚 rollout、修改 retention 策略、确保 SecretRef 或清理运行面资源,都应该先落成 `bun scripts/cli.ts ...` 高层子命令,再由该子命令输出结构化 dry-run、执行摘要、保护对象、后续观察命令和失败分类。
@@ -49,7 +49,7 @@ CI/CD、GitOps、rollout、artifact 发布、PR 合并后的 DEV/PROD 滚动、P
- `dev-env prewarm-images [--image image] [--provider-id D601] [--no-pull] [--proxy-url URL] [--pull-timeout-ms N] [--dry-run]` 创建异步 job,通过 UniDesk SSH 维护桥在 D601 上把开发底座依赖镜像从 Docker 缓存导入原生 k3s containerd。默认镜像是 `postgres:16-alpine``rancher/mirrored-library-busybox:1.36.1`,用于避免 `postgres-dev` 与 local-path helper pod 卡在外部 registry 拉取。该命令固定验证 `/etc/rancher/k3s/k3s.yaml` 指向的 native k3s 上下文,并输出 `dev_env_containerd_image_ready=...` 作为成功判据;它不 apply manifest、不修改生产 `unidesk` namespace。
- `artifact-registry plan|render|status|health|install|deploy-backend-core|deploy-service` 管理 D601 host-managed CNCF Distribution registry 的声明、安装、只读检查和 pull-only artifact CD。该 registry 固定为 D601 loopback `127.0.0.1:5000`,由 systemd + Docker Compose 管理,位于 native k3s 故障域外;`deploy-service` 只拉取 CI 已发布的 commit-pinned 镜像、retag/recreate 或导入 native k3s,并做 live commit 验证,不构建 runtime source。`deploy-backend-core` 是 deprecated 兼容名,标准 backend-core prod CD 入口是 `deploy apply --env prod --service backend-core`。长期规则见 `docs/reference/artifact-registry.md`
- `commander contract|plan --dry-run|smoke --dry-run|approval request --dry-run|prompt-lint --kind gpt55-pr` 是 host Codex 指挥官直管微服务 skeleton 入口。当前命令返回 `phase=source-contract`、service/API/state/bridge/prompt/trace/#20/#46/ClaudeQQ 审批边界、.state/commander/ 状态模型、dev 无 daemon smoke contract、dry-run 计划和 GPT-5.5 PR prompt 边界辅助 lint,不接 live bridge、不注入 prompt、不发送 ClaudeQQ。`approval request --dry-run` 会生成 200 字以内中文纯文本 ClaudeQQ 审批草案、`notification-path-unavailable` blocker 和授权后唯一可用的 `bun scripts/cli.ts microservice proxy claudeqq /api/push/text --method POST --body-json '<payload>' --raw` 命令;不得提示使用本机 ClaudeQQ skill、powershell 或本地 server。`prompt-lint` 支持 `--prompt-file``--stdin`,输出 `ok``missingClauses``riskLevel``suggestedPatchSnippet` 且不回显完整 prompt;它是 commander 辅助检查,不是业务 PR 门禁,也不改变 `codex submit` 默认行为。`plan``smoke``approval request` 必须带 `--dry-run`;缺少时返回 `error=dry-run-required`。长期规则见 `docs/reference/host-codex-commander.md`
- `hwlab g14 monitor-prs [--lane g14|v02] [--once] [--dry-run] [--interval-seconds N] [--max-cycles N] [--timeout-seconds N]` 是当前 HWLAB G14 PR -> CI/CD -> DEV rollout 的一行式入口。普通调用创建 `.state/jobs/` 异步 job 并立刻返回 `job.id``statusCommand` 和 stdout/stderr 路径;后台 worker 每轮通过 UniDesk `gh pr list/preflight/merge` 监控 `pikasTech/HWLAB` base=`G14` 的 open PRready 时合并,然后通过 UniDesk `trans G14:k3s` 观察 `hwlab-g14-ci-poll-<short>`、Argo `hwlab-g14-dev` 和 DEV `/health/live`,直到 DEV `Synced/Healthy` 且 Deployment/StatefulSet ready;历史 `Completed` smoke/debug pod 不作为 rollout blocker。每次成功 DEV rollout 后,worker 会定位或创建 #7“指挥简报索引”中的北京日期每日简报 issue,并追加 CI/CD 耗时、CI/CD 关键指标、语义化上线 changelog、自动 diff 摘要、PipelineRun、GitOps revision 和 DEV 验证摘要;关键指标来自 G14 Tekton TaskRun results,固定包含 `lazy build reused: x/y`、reused services、rebuild services 和每个 service 的独立耗时/状态/backend,用于观察 lazy build 机制效果。语义化 changelog 优先从 PR body 的 `## 修改`/`## 变更`/`## Changelog` 等段落提取,diff 摘要只作为文件和统计证据保留,不替代 changelog。也可用 `hwlab g14 record-rollout --pr <number> --source-commit <sha>` 手动补记,手动补记同样会按 PipelineRun 采集 TaskRun 指标。G14 状态指针按用途分离:长期监控只写 `.state/hwlab-g14/latest-monitor-job.json``--once``latest-once-job.json``--dry-run``latest-dry-run-job.json``--once --dry-run``latest-once-dry-run-job.json`,避免一次性收口覆盖持续监控入口。`--once --dry-run` 只做单轮监控和 merge plan,不写 GitHub、不等待 rollout。该命令禁止使用原生 `gh` 或手拼 GitHub 请求;如果 UniDesk `gh` 子命令字段或行为不够,必须先改进 `scripts/src/gh.ts` 后再使用
- `hwlab g14 retirement status|plan|execute --confirm [--wait]` 是 legacy G14 DEV/PROD 的受控退役入口。`status` 只读报告 `argocd/hwlab-g14-dev``argocd/hwlab-g14-prod``hwlab-dev``hwlab-prod`、bounded legacy resource preview、受保护的 `hwlab-g14-v02`/`hwlab-node-v03``hwlab-v02`/`hwlab-v03`,以及 `.state/hwlab-g14/legacy-g14-retirement.json` marker。`plan` 是 dry-run,只列出 destructive targets 和 protected targets。`execute --confirm` 删除 legacy Argo Applications 与 legacy namespaces,取消本地 active `hwlab_g14_pr_monitor` job,并写 retirement marker 记录执行证据;`hwlab g14 monitor-prs --lane g14` 按退役合同固定结构化失败并指向 `retirement status``--lane v02`。该入口禁止触碰 v0.2/v0.3 Application、namespace、PipelineRun、Secret、Git mirror 或 FRP desired state
- `hwlab g14 monitor-prs --lane v02` 是 HWLAB `v0.2` 的 PR -> CI -> CD 自动化入口。它只监控 base=`v0.2` 的 open PR:每轮先用 UniDesk `gh pr preflight` 读取 GitHub CI/checks、mergeability 和冲突状态;pending 时在 PR 下写等待评论,blocked/conflict 时写阻塞评论;ready 时直接用 UniDesk `gh pr merge` 合并,不因为其他 commit 的运行中 PipelineRun 阻塞 merge 或 CI 启动。合并后执行受控 `control-plane trigger-current --lane v02 --confirm --wait`、轮询定点 `control-plane status --lane v02 --source-commit <merge-sha>`,必要时执行 `git-mirror flush --confirm --wait`。v0.2 CD 采用 latest-only:旧 PipelineRun 不取消、不等待,但 promotion 写 `v0.2-gitops` 前必须重新确认 source headstale commit 只能以 superseded/no-op 收口,不能回滚 runtime。不管 CD 成功、superseded、失败或超时,都在原 PR 下用 `gh pr comment create --body-stdin <<'EOF'` 追加语义化状态,正文固定包含起止时间、总耗时、冲突状态、CI/preflight conclusion、source commit、PipelineRun、targetValidation、Argo/webAssets 和 git mirror pendingFlush/githubInSync。评论去重状态写入 `.state/hwlab-g14/v02-pr-comment-signatures.json`,同一状态签名不会重复刷评论;v0.2 monitor 指针使用 `.state/hwlab-g14/latest-v02-monitor-job.json``latest-v02-once-job.json``latest-v02-dry-run-job.json``latest-v02-once-dry-run-job.json`,不会覆盖默认 G14 monitor 指针。`--lane v02 --once --dry-run` 只做单轮 preflight/merge/CD/comment plan,不写 GitHub、不触发 CD。
- `agentrun v01 control-plane status|trigger-current|refresh|cleanup-runs|cleanup-released-pvs [--dry-run|--confirm]` 是 AgentRun `v0.1` 在 G14 k3s 的受控 Tekton/Argo 入口。`status` 只读汇总固定 source worktree commit、对应 commit-pinned PipelineRun、GitOps latest、Argo Application、`agentrun-v01` manager source commit、`planArtifacts.summary`、env image result 和 git mirror 摘要,并报告 manager/Argo/GitOps 是否对齐当前 source commit。默认输出是 compact commander 视图:`summary` 给出 source、PipelineRun、Argo、manager image、git mirror 和 `aligned` 结论;`timings` 给出 `sourceMs``runtimeMs``gitMirrorMs``totalMs`;远端 stdout/stderr tail 默认省略,失败时仍展开必要 tail,完整 tail 用 `--full`,原始 git mirror cache 用 `--raw``status` 聚合 source 后会并行读取 runtime 和 git mirror,并向 stderr 输出 `agentrun.control-plane.status.progress` JSON 事件,覆盖 `source``runtime``git-mirror` 的 started/succeeded/failed 和 elapsedMs,避免 10s 以上状态聚合期间无可见进展;`trigger-current` 先快进 `G14:/root/agentrun-v01``origin/v0.1`,检查 `devops-infra` mirror 的 `localV01` 是否等于目标 source commit,必要时先执行受控 mirror sync,再创建 `agentrun-v01-ci-<short12>` PipelineRun。confirmed trigger 只提交 CI/CD 工作并返回后续 `status` 命令,不等待完整 PipelineRun;同名 PipelineRun 运行中或已成功时拒绝重复触发,只允许失败态重建或首次创建。`refresh` 只对 `argocd/agentrun-g14-v01` 执行 hard refresh,用于 GitOps promotion 已完成但 Argo 仍停留旧 revision 时的受控同步入口;它不直接 patch runtime workload。`cleanup-runs` 只清理 `agentrun-ci` 中已完成且超过 `--min-age-minutes``agentrun-v01-ci-*` PipelineRun,通过 Tekton ownerRef 回收临时 workspace PVCdry-run 必须输出候选 PipelineRun、owned PVC、active mount 保护、local-path 实际估算 bytes 和 confirm 命令。`cleanup-released-pvs` 只处理 `agentrun-ci``local-path``Delete` reclaim policy 的 `Released` PV,用于 PipelineRun 删除后残留 PV 的二次回收;它不触碰 runtime namespace、业务 PVC、Secret、registry storage 或 GitOps desired state。AgentRun 运行时和 SPEC 事实来源仍在 AgentRun 仓库,UniDesk 只维护受控运维入口。
- `agentrun v01 git-mirror status|sync|flush [--dry-run|--confirm]` 是 AgentRun `v0.1` 使用 `devops-infra` git mirror/relay 的受控维护入口。`status` 默认返回 read/write URL、`localV01``githubV01``localGitops``githubGitops``pendingFlush``githubInSync` 和 exact full-SHA shallow fetch 摘要,不默认展开完整 cache stdout;需要探测 tail 时用 `--full`,需要原始 cache 输出时用 `--raw``sync` 创建 manual Job,把 GitHub `v0.1``v0.1-gitops` refs 拉入 `/cache/pikasTech/agentrun.git``flush` 把本地 `v0.1-gitops` 快进推回 GitHub。confirmed `sync`/`flush` 默认创建 `.state/jobs/` 异步 job 并立刻返回 `job.id``statusCommand` 和日志路径;只有现场同步调试才显式加 `--wait`。该入口与 HWLAB v0.2 mirror 共用 `devops-infra` 服务和 cache PVC,但 repo path、refs、status 文件和 CLI 命令彼此独立。
+26 -24
View File
@@ -1,52 +1,54 @@
# G14 Provider Node
G14 is the current HWLAB DEV/PROD source and k3s/GitOps runtime truth, and it remains a UniDesk provider node for staging other infrastructure workloads. Its UniDesk provider id is `G14`; the local UniDesk worktree is `/root/unidesk`, and the native k3s kubeconfig is `/etc/rancher/k3s/k3s.yaml`.
G14 is the current HWLAB runtime-lane node and a UniDesk provider node for staging other infrastructure workloads. Legacy HWLAB G14 DEV/PROD (`G14`/`G14-gitops`, `hwlab-dev`/`hwlab-prod`, ports 17666/17667 and 18666/18667, Argo Applications `hwlab-g14-dev`/`hwlab-g14-prod`) is retired and must not be used as current source, release, validation, rollback or support truth. G14's UniDesk provider id is `G14`; the local UniDesk worktree is `/root/unidesk`, and the native k3s kubeconfig is `/etc/rancher/k3s/k3s.yaml`.
G14's long-lived k3s control bridge is `k3sctl-adapter-g14`, a UniDesk direct service outside the k3s fault domain. It listens on the G14 host loopback port `127.0.0.1:4266` and is registered separately from the D601 `k3sctl-adapter`, so G14 infrastructure services can be built and tested without taking over user services that still run on D601.
For Code Queue and non-HWLAB CI/CD migration preparation, G14 uses native k3s labels `unidesk.ai/node-id=G14` and `unidesk.ai/provider-id=G14`. The G14 Code Queue manifests `src/components/microservices/k3sctl-adapter/k3s/code-queue.g14.k8s.yaml` and `src/components/microservices/k3sctl-adapter/k3s/code-queue.g14.k3s.json` are candidate staging artifacts only until an explicit production cutover is approved. Non-HWLAB production Code Queue, CI/CD and user-service execution must remain on D601 while D601 is carrying those services.
## HWLAB DEV/PROD Runtime
## Legacy DEV/PROD Retirement
G14 hosts the current HWLAB DEV runtime in native k3s namespace `hwlab-dev`, and the same node/GitOps line is the HWLAB PROD target unless a newer HWLAB repo rule says otherwise. The canonical G14 HWLAB source workspace is `/root/hwlab` on branch `G14`, with `origin` set to `git@github.com:pikasTech/HWLAB.git`; before any G14 HWLAB work, this fixed workspace must be immediately fast-forwarded to the latest `origin/G14`. HWLAB source edits, CI/CD/GitOps script changes, render work, manual polling and runtime validation must be based on that updated workspace through UniDesk SSH passthrough. Do not use `/root/HWLAB`, `/home/ubuntu/hwlab`, `/workspace/hwlab`, D601 workspace or a master-server checkout as persistent HWLAB source truth. G14-local details are mirrored on the node in `/root/docs/hwlab-g14-workspace.md`.
Legacy G14 DEV/PROD is a retired runtime, not a stable baseline. The controlled UniDesk entry is:
```bash
bun scripts/cli.ts hwlab g14 retirement status
bun scripts/cli.ts hwlab g14 retirement plan
bun scripts/cli.ts hwlab g14 retirement execute --confirm
```
`status` reports the legacy Argo Applications, legacy namespaces, bounded resource previews, protected v0.2/v0.3 Applications and namespaces, and the local marker at `.state/hwlab-g14/legacy-g14-retirement.json`. `plan` is dry-run only and lists the exact destructive targets. `execute --confirm` deletes only `argocd/hwlab-g14-dev`, `argocd/hwlab-g14-prod`, namespaces `hwlab-dev`, `hwlab-prod`, and active local `hwlab_g14_pr_monitor` jobs; it must not touch `hwlab-g14-v02`, `hwlab-node-v03`, `hwlab-v02`, or `hwlab-v03`. The legacy base=`G14` PR monitor is blocked by this retirement contract even in a fresh checkout; the marker is execution evidence, not the source of the policy.
The old `/root/hwlab` workspace on branch `G14` is no longer a default source truth. Use it only for explicit legacy archaeology or a user-authorized retirement follow-up, after fast-forwarding and reading the HWLAB repo rules. Current source, render, CI/CD and validation work must use the target runtime lane workspace such as `G14:/root/hwlab-v02` for v0.2 or the node-scoped lane configuration for v0.3+.
The standard entry forms are:
```bash
trans G14:/root/hwlab script -- 'git fetch origin G14 && git pull --ff-only origin G14 && git status --short --branch && git remote -v'
trans G14 apply-patch < patch.diff
trans G14:k3s kubectl get pods -n hwlab-dev
trans G14:/root/hwlab apply-patch < patch.diff
trans G14:k3s kubectl get pods -n hwlab-v02
```
`G14:k3s` is the only supported k3s route form. Do not use `ssh G14 k3s ...`; the first token must locate the distributed target, and the following tokens must be the operation.
If `/root/hwlab` has unrelated local changes when this sync starts, first determine whether they can be quickly merged with the latest `origin/G14`. Merge them immediately when they are mergeable; do not default to stash, discard or a behind worktree. Only when the changes cannot be automatically merged should they be isolated and the operation stopped for human decision. A behind fixed workspace is not a valid basis for precheck, new worktree creation, render, polling, deployment or runtime validation.
The G14 HWLAB runtime boundary is:
The retired G14 DEV/PROD boundary is:
- Current DEV public endpoints are `http://74.48.78.17:17666/` and `http://74.48.78.17:17667/health/live`. D601 `16666/16667` is legacy/migration evidence only.
- Legacy public endpoints `http://74.48.78.17:17666/`, `http://74.48.78.17:17667/health/live`, `http://74.48.78.17:18666/`, and `http://74.48.78.17:18667/health/live` are not current validation targets.
- Keep HWLAB Services as `ClusterIP` unless a repo-owned G14 GitOps rule explicitly exposes them. Public exposure should stay in the approved G14 edge/proxy path, not ad hoc NodePort or local port-forward.
- Use a G14-local PostgreSQL instance such as `hwlab-g14-postgres` and a G14-local `hwlab-cloud-api-dev-db` Secret for cloud-api durable runtime tests. Do not copy D601 database credentials.
- Use runtime-lane local PostgreSQL and runtime-lane Secrets for cloud-api durable runtime tests. Do not copy D601 database credentials.
- Use only G14-local Codex auth material and k8s Secrets authorized for HWLAB on G14; do not copy D601 or production auth material by hand.
- Set `HWLAB_CLOUD_API_PORT=6667` explicitly in the G14 cloud-api Deployment. Kubernetes otherwise injects a `HWLAB_CLOUD_API_PORT=tcp://...` Service environment variable that breaks the Node port parser.
- `HWLAB_PUBLIC_ENDPOINT` and health/live evidence must describe the G14 endpoint, not the old D601 production endpoint.
- Do not run HWLAB repository `check`, Playwright/browser smoke, image builds or other heavy validation on the master server. Run those through G14 `/root/hwlab`, G14 k3s/Tekton, or another explicitly approved external execution plane.
- Manual device-agent experiments for real hardware must be standalone resources in `hwlab-dev` such as `device-agent-71-freq` and must not patch existing HWLAB Deployments, Services, ArgoCD Applications, FRP, CD desired-state or public frontend routing unless a separate HWLAB change authorizes it.
- A D601 Windows `hwlab-gateway` may connect outbound to G14 DEV cloud-api as an external host bridge for Keil/serial/workspace access. That bridge does not make D601 the HWLAB runtime truth; it is only a hardware access provider behind the G14 device-agent/cloud-api path.
- `HWLAB_PUBLIC_ENDPOINT` and health/live evidence must describe the active runtime lane endpoint, not retired G14 DEV/PROD or old D601 production endpoints.
- Do not run HWLAB repository `check`, Playwright/browser smoke, image builds or other heavy validation on the master server. Run those through the target G14 runtime lane workspace, G14 k3s/Tekton, or another explicitly approved external execution plane.
- Manual device-agent experiments for real hardware must be standalone resources in the active runtime lane namespace and must not patch existing HWLAB Deployments, Services, ArgoCD Applications, FRP, CD desired-state or public frontend routing unless a separate HWLAB change authorizes it.
- A D601 Windows `hwlab-gateway` may connect outbound to the active G14 runtime lane cloud-api as an external host bridge for Keil/serial/workspace access. That bridge does not make D601 the HWLAB runtime truth; it is only a hardware access provider behind the G14 device-agent/cloud-api path.
After the G14-local database is provisioned, run the HWLAB migration CLI only against the G14 DEV database with explicit non-production confirmations:
```bash
kubectl -n hwlab-dev exec deploy/hwlab-cloud-api -- \
node /app/cmd/hwlab-cloud-api/migrate.mjs \
--apply --confirm-dev --confirmed-non-production
```
Healthy G14 HWLAB runtime means the main Deployments and StatefulSets are Ready, `cloud-api` and `edge-proxy` return `/health/live` with `status=ok`, durable runtime checks pass, and the public G14 DEV endpoints report the expected revision. For a device-agent smoke, health also requires the standalone device-agent Service to answer in-cluster and the D601 Windows gateway session/resource/capability to be visible through G14 cloud-api.
Healthy G14 HWLAB runtime means the active runtime lane's main Deployments and StatefulSets are Ready, `cloud-api` and `edge-proxy` return `/health/live` with `status=ok`, durable runtime checks pass, and the public lane endpoints report the expected revision. For a device-agent smoke, health also requires the standalone device-agent Service to answer in-cluster and the D601 Windows gateway session/resource/capability to be visible through the active G14 cloud-api.
## HWLAB v0.2 Expansion Line
HWLAB `v0.2` is an additive G14 expansion line. It must not rename, delete, repurpose, or mutate the existing `G14` source branch, `G14-gitops` branch, `hwlab-dev` namespace, `hwlab-prod` namespace, DEV public ports `17666/17667`, or PROD public ports `18666/18667`. Existing DEV/PROD CI/CD remains the stability baseline while `v0.2` is introduced beside it.
HWLAB `v0.2` is the current supported G14 runtime lane for the v0.2 branch. It must not recreate, depend on, or roll back to the retired legacy `G14` DEV/PROD runtime. Legacy `G14`/`G14-gitops`, `hwlab-dev`/`hwlab-prod`, DEV public ports `17666/17667`, and PROD public ports `18666/18667` are not the stability baseline.
The fixed `v0.2` source branch is `v0.2`, forked from the current `G14` branch after the G14 long-term reference docs record this decision. The fixed G14 development workspace for that branch is:
@@ -54,7 +56,7 @@ The fixed `v0.2` source branch is `v0.2`, forked from the current `G14` branch a
trans G14:/root/hwlab-v02 script -- 'git status --short --branch && git remote -v'
```
`/root/hwlab-v02` is the long-lived `v0.2` development workspace, not a scratch clone or CI/CD source selector. It must track `origin/v0.2` with `origin git@github.com:pikasTech/HWLAB.git`; local dirty state, stale `HEAD`, and untracked `.worktree/` only affect human development. Existing `G14` work continues to use `/root/hwlab`; do not reuse `/root/hwlab` or `/root/hwlab/.worktree/*` as the `v0.2` fixed workspace.
`/root/hwlab-v02` is the long-lived `v0.2` development workspace, not a scratch clone or CI/CD source selector. It must track `origin/v0.2` with `origin git@github.com:pikasTech/HWLAB.git`; local dirty state, stale `HEAD`, and untracked `.worktree/` only affect human development. Do not reuse retired `/root/hwlab` or `/root/hwlab/.worktree/*` as the `v0.2` fixed workspace.
`v0.2` CI/CD source selection is isolated in the dedicated bare repo `G14:/root/hwlab-v02-cicd.git`. UniDesk control-plane commands must fetch `origin/v0.2` into that repo and render from a commit-pinned detached worktree; they must not read the source commit from `/root/hwlab-v02` checkout state.
@@ -65,7 +67,7 @@ The fixed `v0.2` runtime namespace is `hwlab-v02`. The intended public FRP alloc
Master-side FRP server maintenance for HWLAB public ports is documented in `docs/reference/hwlab.md#hwlab-frp-维护`; keep the detailed allowlist, restart boundary and verification sequence there instead of duplicating another runbook in this G14 node reference.
The `v0.2` CI/CD integration must be additive: add a manual UniDesk trigger, dedicated CI/CD source repo, `devops-infra` git mirror/relay, GitOps desired-state lane, Argo CD Application, namespace resources, artifact catalog, and a `deploy/deploy.yaml` lane config only when they target `v0.2`/`hwlab-v02` explicitly. Do not add a `v0.2` branch poller or retarget the existing `G14` poller, DEV/PROD Argo Applications, DEV/PROD runtime paths, or existing namespace resources to bootstrap `v0.2`.
The `v0.2` CI/CD integration owns a dedicated CI/CD source repo, `devops-infra` git mirror/relay, GitOps desired-state lane, Argo CD Application, namespace resources, artifact catalog, and a `deploy/deploy.yaml` lane config only when they target `v0.2`/`hwlab-v02` explicitly. Do not add or revive a legacy `G14` branch poller, DEV/PROD Argo Applications, DEV/PROD runtime paths, or existing namespace resources to bootstrap `v0.2`.
For current G14/v0.2, `deploy/deploy.yaml` is the single human-authored deploy/runtime config source. `deploy/deploy.json` is not a v0.2 compatibility source and must not be recreated for this lane. YAML and JSON parsing are centralized in the HWLAB repo's format-agnostic config layer: `scripts/src/structured-config.mjs` handles file format parsing/writing, while `scripts/src/deploy-config.mjs` owns deploy-config defaults and shape. Renderers, planners, smoke scripts and CLIs should consume that layer through `readStructuredFile` / `writeStructuredFile` / `readDeployConfig` or equivalent helpers; do not scatter direct YAML parser imports or ad hoc `readFile` + `YAML.parse` calls. Legacy D601 HWLAB CD still has its own `deploy/deploy.json` desired-state documented in `docs/reference/hwlab.md`; that legacy path is not the G14/v0.2 authority.
+26 -27
View File
@@ -5,13 +5,13 @@
## 固定入口
- UniDesk 指挥侧 workspace`/root/unidesk`,固定使用 `master`,开始前执行 `git status``git pull --ff-only origin master`
- HWLAB 当前主阵地:`G14:HWLAB`,唯一长期 source workspace 是 G14 节点上的 `/root/hwlab`,固定使用 `G14` 分支和 `origin git@github.com:pikasTech/HWLAB.git`。新开发、文档、render、polling、CI/CD、GitOps、DEV/PROD runtime 和验收默认都以这个 workspace 和 G14 k3s 为准
- HWLAB `v0.2` 是 G14 上的加法扩容线,固定分支为 `v0.2`,固定开发 workspace 为 `G14:/root/hwlab-v02`,固定 CI/CD source repo 为 `G14:/root/hwlab-v02-cicd.git`,固定 namespace 为 `hwlab-v02`,公网入口规划`http://74.48.78.17:19666/``http://74.48.78.17:19667/health/live`。创建和集成 `v0.2` 时不得删除、重命名或改义现有 `G14`/`G14-gitops` 分支、`hwlab-dev`/`hwlab-prod` namespace 或 17666/17667、18666/18667 入口;细则见 `docs/reference/g14.md`
- HWLAB 项目内长期规则入口`/root/hwlab/AGENTS.md`。开始 `G14:HWLAB` 分布式开发、切换任务、恢复中断或上下文压缩后,必须重新读取文件;不能只凭主 server 的压缩上下文继续操作。
- 每次开始 G14 工作前必须通过 UniDesk SSH 桥执行 `cd /root/hwlab && git status --short --branch && git remote -v`,期望分支是 `G14...origin/G14`;若不满足,先修正 workspace,不能继续开发、render、polling 或部署。
- G14 k3s 操作必须使用 route 语法 `G14:k3s`,例如 `trans G14:k3s kubectl get pods -n hwlab-dev`;禁止写成 `ssh G14 k3s ...`。第一个 route token 必须定位分布式目标,后续 token 才是 operation。
- HWLAB legacy G14 DEV/PROD 已退役;`G14`/`G14-gitops``hwlab-dev`/`hwlab-prod`、17666/17667 和 18666/18667 不再是当前 source/runtime truth。退役状态和执行入口是 `bun scripts/cli.ts hwlab g14 retirement status|plan|execute --confirm`,细则见 `docs/reference/g14.md`
- HWLAB 当前 G14 主阵地是 runtime lane`v0.2` 固定分支为 `v0.2`,固定开发 workspace 为 `G14:/root/hwlab-v02`,固定 CI/CD source repo 为 `G14:/root/hwlab-v02-cicd.git`,固定 namespace 为 `hwlab-v02`,公网入口为 `http://74.48.78.17:19666/``http://74.48.78.17:19667/health/live``v0.3+` `hwlab nodes --node G14 --lane vNN``config/hwlab-node-lanes.yaml` 管理
- HWLAB 项目内长期规则入口仍以目标 repo 的 `AGENTS.md` 为准。进入 G14 runtime lane 分布式开发、切换任务、恢复中断或上下文压缩后,必须重新读取目标 workspace 的规则文件;不能只凭主 server 的压缩上下文继续操作。
- 每次开始 G14 runtime lane 工作前必须通过 UniDesk SSH 桥检查目标 workspace,例如 `trans G14:/root/hwlab-v02 script -- 'git status --short --branch && git remote -v'`;若不满足目标 lane 预期,先修正 workspace,不能继续开发、render、polling 或部署。
- G14 k3s 操作必须使用 route 语法 `G14:k3s`,例如 `trans G14:k3s kubectl get pods -n hwlab-v02`;禁止写成 `ssh G14 k3s ...`。第一个 route token 必须定位分布式目标,后续 token 才是 operation。
- `D601:HWLAB` 已降级为 legacy/migration 对照和 D601 Windows 硬件 bridge 入口,不再是默认开发主阵地或运行面真相。只有用户明确指定 D601 legacy、D601 回滚对照或 D601 Windows/Keil/串口硬件桥接时,才使用 `/home/ubuntu/workspace/hwlab-dev`;进入前仍要执行 `cd /home/ubuntu/workspace/hwlab-dev && git status --short --branch && git remote -v` 并读取该目录的 `AGENTS.md`
- D601 上 `/home/ubuntu/workspace/hwlab``/home/ubuntu/hwlab``/tmp/hwlab-*``/home/ubuntu/workspace/hwlab-*`、master-server checkout 或其他 runner clone 都不能作为当前 HWLAB source truth。发现 D601 口径与 G14 `/root/hwlab/AGENTS.md` 冲突时,以 G14 当前规则为准。
- D601 上 `/home/ubuntu/workspace/hwlab``/home/ubuntu/hwlab``/tmp/hwlab-*``/home/ubuntu/workspace/hwlab-*`、master-server checkout 或其他 runner clone 都不能作为当前 HWLAB source truth。发现 D601 口径与当前 G14 runtime lane 规则冲突时,以 G14 runtime lane 规则为准。
- `/root/HWLAB``/workspace/hwlab`、D601 workspace、master-server checkout 或临时 clone 都不能作为 `G14:HWLAB` source truth。
## 关键 GitHub 入口
@@ -27,62 +27,61 @@
## DEV 入口
- 当前 G14 DEV 前端入口固定为 `http://74.48.78.17:17666/`
- 当前 G14 DEV API/live 入口固定为 `http://74.48.78.17:17667/health/live`
- 规划中的 G14 `v0.2` 前端/API 入口固定为 `http://74.48.78.17:19666/``http://74.48.78.17:19667/health/live`,只能指向 `hwlab-v02` namespace,不得复用或覆盖 DEV/PROD 入口。
- 退役 G14 DEV/PROD 前端/API 入口曾使用 `http://74.48.78.17:17666/``http://74.48.78.17:17667/health/live``http://74.48.78.17:18666/``http://74.48.78.17:18667/health/live`;这些端口不再作为当前 HWLAB runtime 证据
- 当前 G14 `v0.2` 前端/API 入口固定为 `http://74.48.78.17:19666/``http://74.48.78.17:19667/health/live`,只能指向 `hwlab-v02` namespace
- D601 legacy DEV 前端/API 入口曾使用 `http://74.48.78.17:16666/``http://74.48.78.17:16667/health/live`;只在显式 D601 legacy 排障或迁移对照时使用,不能作为当前 HWLAB DEV 运行面证据。
- 旧公网 `:6666``:6667` 不是浏览器验收入口;内部 k3s service 仍可使用 `6667` 作为服务端口。
- 当前 G14 入口由 G14 侧公开代理承担;不要把 master 上的其他 UniDesk frontend/backend-core 路径误判为 HWLAB 前端。D601 legacy `16666/16667` 的 FRP 说明只用于历史对照。
- 当前 G14 runtime lane 入口由 G14 侧公开代理承担;不要把 master 上的其他 UniDesk frontend/backend-core 路径误判为 HWLAB 前端。D601 legacy `16666/16667` 和 retired G14 DEV/PROD 端口的 FRP 说明只用于历史对照。
## HWLAB FRP 维护
HWLAB 公网 FRP server 由 master server 上的 `hwlab-frps-dev` 容器承担,容器使用 host network,并把 `/opt/hwlab-frp/frps.dev.toml` 只读挂载到 `/etc/frp/frps.toml`。这个 server 侧 allowlist 是 UniDesk 指挥侧维护对象,不属于 G14 k3s GitOps desired stateG14 侧 `frpc` ConfigMap/Deployment 只负责各 runtime namespace 的客户端 tunnel。
新增或恢复 HWLAB 公网入口时,先判断问题是否只是 server 侧 `allowPorts` 缺口。若 `frpc` 日志出现 `port not allowed`,优先在 master server 修改 `/opt/hwlab-frp/frps.dev.toml``allowPorts`,而不是改 DEV/PROD/v0.2 的 GitOps、Service 或 `frpc` ConfigMap。修改前保留一份本机备份,例如 `/opt/hwlab-frp/frps.dev.toml.bak-<reason>`;修改后只重启 `hwlab-frps-dev`,不要重建镜像、清理 Docker volume、重启无关 UniDesk/HWLAB 服务或触碰数据库状态。
新增或恢复 HWLAB 公网入口时,先判断问题是否只是 server 侧 `allowPorts` 缺口。若 `frpc` 日志出现 `port not allowed`,优先在 master server 修改 `/opt/hwlab-frp/frps.dev.toml``allowPorts`,而不是改 active runtime lane 的 GitOps、Service 或 `frpc` ConfigMap。修改前保留一份本机备份,例如 `/opt/hwlab-frp/frps.dev.toml.bak-<reason>`;修改后只重启 `hwlab-frps-dev`,不要重建镜像、清理 Docker volume、重启无关 UniDesk/HWLAB 服务或触碰数据库状态。
当前 HWLAB FRP server allowlist 至少应覆盖 legacy `16666/16667`、G14 DEV `17666/17667`、G14 PROD `18666/18667`G14 `v0.2` `19666/19667`,以及仍被明确使用的维护端口。新增端口必须对应一个明确的 namespace/runtime 入口,不能把大范围端口段作为默认放行策略。
当前 HWLAB FRP server allowlist 至少应覆盖 active runtime lane 端口(例如 G14 `v0.2` `19666/19667`)以及仍被明确使用的维护端口。Legacy `16666/16667` 和 retired G14 DEV/PROD `17666/17667``18666/18667` 只作为历史对照,不应作为新增 runtime 入口要求。新增端口必须对应一个明确的 namespace/runtime 入口,不能把大范围端口段作为默认放行策略。
FRP 维护验证顺序是:确认 `hwlab-frps-dev` 容器仍挂载 `/opt/hwlab-frp/frps.dev.toml`;重启后用 `ss -ltnp` 或等价只读命令确认 `frps` 正在监听 `7000` 和目标公网端口;再在目标 namespace 查看 `frpc` 日志,确认对应 proxy `start proxy success`;最后分别验证新增入口和既有 DEV 入口。`v0.2` 验证使用 `http://74.48.78.17:19666/``http://74.48.78.17:19667/health/live`DEV 回归验证使用 `http://74.48.78.17:17666/``http://74.48.78.17:17667/health/live`
FRP 维护验证顺序是:确认 `hwlab-frps-dev` 容器仍挂载 `/opt/hwlab-frp/frps.dev.toml`;重启后用 `ss -ltnp` 或等价只读命令确认 `frps` 正在监听 `7000` 和目标公网端口;再在目标 namespace 查看 `frpc` 日志,确认对应 proxy `start proxy success`;最后验证 active runtime lane 入口。`v0.2` 验证使用 `http://74.48.78.17:19666/``http://74.48.78.17:19667/health/live`
FRP 文档、issue 和日志只能记录端口、容器名、ConfigMap 名、Secret 对象/key 是否存在和健康摘要;不得记录 Secret value、provider token、完整 DB URL、Codex auth JSON 或其他凭据内容。
## 门禁最小化与扩容治理
不要滑向不必要的复杂门禁是 HWLAB 指挥侧通用原则。G14 DEV/PROD`v0.2` 扩容、CI/CD 迁移、运行面热修和文档治理都应优先靠固定边界、清晰命名、唯一真相源、标准入口和长期参考文档收敛,不要把每个设计约定、运行策略或回滚手册都做成新的 preflight、guard、gate 或报告生成器。
不要滑向不必要的复杂门禁是 HWLAB 指挥侧通用原则。Legacy G14 DEV/PROD 退役、runtime lane 扩容、CI/CD 迁移、运行面热修和文档治理都应优先靠固定边界、清晰命名、唯一真相源、标准入口和长期参考文档收敛,不要把每个设计约定、运行策略或回滚手册都做成新的 preflight、guard、gate 或报告生成器。
旧 DEV/D601/main 门禁如果阻碍当前 G14 或 `v0.2` 路径,默认处理是从当前调用链删除,而不是做兼容迁移、fallback、legacy mode、双路径绕行或在旧门禁上叠加例外。新增门禁只能覆盖明确高价值风险,且必须最小、低噪声、容易删除;资源配额、RBAC 命名、清理策略、回滚顺序、人工同步策略等默认是设计约定或 runbook,不是 CI/CD 通过条件。
`v0.2` 扩容时指挥侧只把以下内容视为硬边界:source branch 是 `v0.2`、CI/CD source repo 是 `G14:/root/hwlab-v02-cicd.git`、GitOps branch 是 `v0.2-gitops`、runtime namespace 是 `hwlab-v02`、runtime path 是 `deploy/gitops/g14/runtime-v02`、公网入口是 `19666/19667``v0.2` source branch 不跟踪生成物、旧 DEV/D601/main 门禁不进入 `v0.2` 调用链。`/root/hwlab-v02` 是人工开发和短连接源码工具 workspacedirty/stale 状态不得影响 CI/CD source commit 选择。其他事项应先作为决策表或 runbook 固化,只有被证明无法靠边界和标准入口自然收敛时,才允许加最小检查。
`v0.2` runtime lane 只把以下内容视为硬边界:source branch 是 `v0.2`、CI/CD source repo 是 `G14:/root/hwlab-v02-cicd.git`、GitOps branch 是 `v0.2-gitops`、runtime namespace 是 `hwlab-v02`、runtime path 是 `deploy/gitops/g14/runtime-v02`、公网入口是 `19666/19667``v0.2` source branch 不跟踪生成物、旧 DEV/D601/main 门禁不进入 `v0.2` 调用链。`/root/hwlab-v02` 是人工开发和短连接源码工具 workspacedirty/stale 状态不得影响 CI/CD source commit 选择。其他事项应先作为决策表或 runbook 固化,只有被证明无法靠边界和标准入口自然收敛时,才允许加最小检查。
## 最小 Device Agent/Gateway 桥接模型
最小打通目标是只新增桥接面,不改动既有 HWLAB 应用、GitOps、FRP、CD 或前端路径:
- 在 `G14:k3s``hwlab-dev` namespace 手动建立一个 standalone `device-agent-71-freq` Pod/Deployment 和 ClusterIP Service。它暴露设备语义入口,例如 `/health``/skills``/workspace/*``/run`,并把真实硬件调用转成 HWLAB cloud-api 的 gateway operation,而不是在 Pod 内直接访问 Windows 硬件。
- 在 `D601:win` 上启动 `hwlab-gateway`,作为 71-FREQ/ConStart/Keil/串口资源的外部 host bridge。gateway 通过公网或受控网络出站连接当前 G14 DEV cloud-api`http://74.48.78.17:17667`,注册稳定的 `gatewaySessionId``resourceId``capabilityId`
- 在 active G14 runtime lane namespace 手动建立一个 standalone `device-agent-71-freq` Pod/Deployment 和 ClusterIP Service。它暴露设备语义入口,例如 `/health``/skills``/workspace/*``/run`,并把真实硬件调用转成 HWLAB cloud-api 的 gateway operation,而不是在 Pod 内直接访问 Windows 硬件。
- 在 `D601:win` 上启动 `hwlab-gateway`,作为 71-FREQ/ConStart/Keil/串口资源的外部 host bridge。gateway 通过公网或受控网络出站连接当前 G14 runtime lane cloud-api,例如 v0.2 的 `http://74.48.78.17:19667`,注册稳定的 `gatewaySessionId``resourceId``capabilityId`
- `hwlab-gateway` 是 Windows 长驻 outbound poll 进程,不能用 `trans D601:win cmd start ...` 或 PowerShell `Start-Process` 直接挂在一次性 trans/tran 会话下;这种启动方式可能继承输出句柄或被 provider 按子进程树等待,导致 trans/tran 卡住并阻塞后续 provider session。临时实验也应通过 Windows Task Scheduler、Windows Service、NSSM、PM2 service 或等价 detached launcher 启动,`trans` 只负责触发和读取状态;通用规则见 `docs/reference/windows-passthrough.md`
- D601 Windows gateway 的最小配置必须来自 profile 或环境变量,核心字段是 `HWLAB_GATEWAY_CLOUD_URL=http://74.48.78.17:17667``HWLAB_GATEWAY_ID``HWLAB_GATEWAY_SESSION_ID``HWLAB_GATEWAY_RESOURCE_ID``HWLAB_GATEWAY_CMD_CAPABILITY_ID``HWLAB_GATEWAY_CMD_EXEC_ENABLED=1``HWLAB_GATEWAY_DEMO_OPEN=1``HWLAB_GATEWAY_MAX_INFLIGHT``HWLAB_GATEWAY_CMD_TIMEOUT_MS`。这些值用于声明能力和执行边界,不得散落在 device-agent 代码里。
- device-agent 访问 G14 集群内 cloud-api 时优先使用 `http://hwlab-cloud-api.hwlab-dev.svc.cluster.local:6667`。Node/undici `fetch` 会按 Fetch bad-port 规则拒绝 `6667`,因此 Node 实现必须使用 `node:http`/`node:https`、已有 repo-owned HTTP helper,或改用不触发 bad-port 的受控代理端口。
- D601 Windows gateway 的最小配置必须来自 profile 或环境变量,核心字段是 `HWLAB_GATEWAY_CLOUD_URL=<active-runtime-lane-cloud-api>``HWLAB_GATEWAY_ID``HWLAB_GATEWAY_SESSION_ID``HWLAB_GATEWAY_RESOURCE_ID``HWLAB_GATEWAY_CMD_CAPABILITY_ID``HWLAB_GATEWAY_CMD_EXEC_ENABLED=1``HWLAB_GATEWAY_DEMO_OPEN=1``HWLAB_GATEWAY_MAX_INFLIGHT``HWLAB_GATEWAY_CMD_TIMEOUT_MS`。这些值用于声明能力和执行边界,不得散落在 device-agent 代码里。
- device-agent 访问 G14 集群内 cloud-api 时优先使用目标 runtime lane 的 Service DNS,例如 `http://hwlab-cloud-api.hwlab-v02.svc.cluster.local:6667`。Node/undici `fetch` 会按 Fetch bad-port 规则拒绝 `6667`,因此 Node 实现必须使用 `node:http`/`node:https`、已有 repo-owned HTTP helper,或改用不触发 bad-port 的受控代理端口。
- 71-FREQ 的 device profile 必须配置化保存,至少包含工程根目录、Keil project/target、默认串口波特率、gateway/resource/capability 选择器和 workspace 根。不得把 `F:\Work\ConStart`、工程名、串口号或 probe UID 硬编码进 device-agent 镜像。
- 最小验收顺序是:G14 `hwlab-dev` cloud-api `/health/live` readyD601 Windows 能访问 `http://74.48.78.17:17667/health/live`D601 `hwlab-gateway` 注册后 cloud-api 能看到 gateway session/resource/capabilityG14 `device-agent-71-freq` `/health``/skills` 返回 ready;通过 device-agent 发起一条只读或 `echo` 级 gateway shell operation,并返回 operation/evidence/trace 摘要。
- 最小验收顺序是:active G14 runtime lane cloud-api `/health/live` readyD601 Windows 能访问该 lane 的 public `/health/live`D601 `hwlab-gateway` 注册后 cloud-api 能看到 gateway session/resource/capabilityG14 `device-agent-71-freq` `/health``/skills` 返回 ready;通过 device-agent 发起一条只读或 `echo` 级 gateway shell operation,并返回 operation/evidence/trace 摘要。
- 这个模型只证明 `device-agent Pod -> G14 cloud-api -> D601 hwlab-gateway -> Windows host` 的控制链路。Keil 编译下载、串口日志抓取和 workspace CRUD 可以作为后续 device-agent skill 子命令逐步接入;未接入前不能把 device-agent 标成完整 71-FREQ 硬件代理。
## Master Server 校验边界
- master server 是 UniDesk/HWLAB 的生产入口且资源紧张;它只能承担轻量源码编辑、Git 操作、日志/健康观察、JSON CLI 指挥和受控 CD 审阅,不能承担正式校验执行面。
- 禁止在 master server 上运行 HWLAB 或 UniDesk 的仓库级 `check`/`test`/smoke 命令,包括但不限于 `bun scripts/cli.ts check``node --test``node web/hwlab-cloud-web/scripts/check.mjs``node scripts/dev-cloud-workbench-smoke.mjs`、Playwright/browser layout smoke,以及其他会长时间占用 CPU/内存、启动浏览器或遍历大仓库的校验流程。
- 需要正式验证时,固定切到 G14 `/root/hwlab`、G14 k3s/Tekton、HWLAB repo-owned CI 或其他获批外部执行面;master server 只负责发起、观察和记录,不负责实际跑 check。
- 如果为了排障必须从 master server 生成命令或查看源码,后续验证命令也必须显式改到 G14 路径执行,例如 `trans G14:/root/hwlab script -- ...``trans G14:k3s ...`,而不是直接在 `/root/unidesk` 或 master server 上本地运行。
- 需要正式验证时,固定切到目标 G14 runtime lane workspace、G14 k3s/Tekton、HWLAB repo-owned CI 或其他获批外部执行面;master server 只负责发起、观察和记录,不负责实际跑 check。
- 如果为了排障必须从 master server 生成命令或查看源码,后续验证命令也必须显式改到目标 G14 runtime lane 路径执行,例如 `trans G14:/root/hwlab-v02 script -- ...``trans G14:k3s ...`,而不是直接在 `/root/unidesk` 或 master server 上本地运行。
## G14 运行面与 D601 Legacy 口径
HWLAB 当前真实 DEV/PROD runtime 在 G14 原生 k3s 中。只读观察使用:
HWLAB 当前真实 runtime lane 在 G14 原生 k3s 中。只读观察使用:
```sh
trans G14:k3s kubectl -n hwlab-dev get deploy,svc,pod -o wide
trans G14:k3s kubectl -n hwlab-v02 get deploy,svc,pod -o wide
```
`hwlab-cloud-api``hwlab-edge-proxy` 在集群内使用 `6667` Service 端口,对外 DEV API 使用 `17667`。G14 local details 见 `docs/reference/g14.md` 和 G14 节点 `/root/docs/hwlab-g14-workspace.md`
`hwlab-cloud-api``hwlab-edge-proxy` 在集群内使用 `6667` Service 端口,对外 v0.2 API 使用 `19667`。G14 local details 见 `docs/reference/g14.md` 和 G14 节点 `/root/docs/hwlab-g14-workspace.md`
D601 原生 k3s 口径只用于 legacy 对照、迁移排障或明确指定的 D601 回滚任务。此时必须显式使用:
@@ -124,7 +123,7 @@ profile 配置后的最小真实验收是通过同一 HWLAB v0.2 Cloud API/Web d
## D601 Legacy HWLAB DEV CD Wrapper
以下 UniDesk wrapper 是旧 D601 DEV CD 指挥入口,只用于显式 legacy 诊断和迁移对照。当前 G14 DEV/PROD 发布、GitOps 和运行面收敛必须优先按 G14 `/root/hwlab` 与 HWLAB repo-owned 规则执行;不要把下面的 D601 wrapper 当作当前 HWLAB release truth。
以下 UniDesk wrapper 是旧 D601 DEV CD 指挥入口,只用于显式 legacy 诊断和迁移对照。当前 HWLAB 发布、GitOps 和运行面收敛必须优先按 G14 active runtime lane 与 HWLAB repo-owned 规则执行;不要把下面的 D601 wrapper 当作当前 HWLAB release truth。
本节里的 `deploy/deploy.json` 只属于 D601 legacy DEV CD wrapper。当前 G14/v0.2 的单一人写部署配置源是 `deploy/deploy.yaml`,并由 HWLAB repo 的 format-agnostic config layer 读取;权威规则在 `docs/reference/g14.md#hwlab-v02-expansion-line`
@@ -172,7 +171,7 @@ node scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production -
## D601 Legacy Cloud Web 手动 DEV 发布路径
以下路径是 D601 legacy DEV 的历史手动发布基准,只用于迁移对照或显式 D601 回滚排障。当前 G14 DEV/PROD 发布必须优先按 G14 `/root/hwlab`、G14 GitOps 和 HWLAB repo-owned 规则执行;不要把这里的 D601 命令当作当前发布入口。
以下路径是 D601 legacy DEV 的历史手动发布基准,只用于迁移对照或显式 D601 回滚排障。当前 HWLAB 发布必须优先按 G14 active runtime lane、G14 GitOps 和 HWLAB repo-owned 规则执行;不要把这里的 D601 命令当作当前发布入口。
1. 更新 D601 部署副本:
+15
View File
@@ -49,6 +49,21 @@ assertCondition(
"v0.2 control-plane help must expose targeted PipelineRun/source-commit status, closeout inspection, and cleanup",
hwlabHelpUsage,
);
assertCondition(
hwlabHelpUsage.some((line) => line.includes("hwlab g14 retirement status"))
&& hwlabHelpUsage.some((line) => line.includes("hwlab g14 retirement plan"))
&& hwlabHelpUsage.some((line) => line.includes("hwlab g14 retirement execute --confirm"))
&& hwlabHelpJson.includes("legacy-g14-retirement.json")
&& hwlabHelpJson.includes("legacy DEV/PROD retirement"),
"G14 help must expose the controlled legacy DEV/PROD retirement status, dry-run plan, confirmed execution path, and marker",
hwlabHelp,
);
assertCondition(
hwlabHelpUsage.every((line) => !line.includes("record-rollout"))
&& hwlabHelpJson.includes("legacy base=G14 monitor is blocked by the retirement contract"),
"G14 help must not advertise retired legacy DEV rollout support paths",
hwlabHelp,
);
assertCondition(
hwlabHelpUsage.some((line) => line.includes("hwlab nodes control-plane status --node G14 --lane v03"))
&& hwlabHelpUsage.some((line) => line.includes("hwlab nodes control-plane apply --node G14 --lane v03 --dry-run"))
+405 -14
View File
@@ -3,7 +3,7 @@ import { dirname, join } from "node:path";
import { createHash, randomBytes } from "node:crypto";
import { repoRoot, rootPath, type Config } from "./config";
import { runCommand } from "./command";
import { readJob, startJob } from "./jobs";
import { cancelJob, listJobs, readJob, startJob } from "./jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "./hwlab-node-lanes";
const HWLAB_REPO = "pikasTech/HWLAB";
@@ -15,9 +15,11 @@ const V02_SOURCE_BRANCH = V02_LANE_SPEC.sourceBranch;
const V02_WORKSPACE = V02_LANE_SPEC.workspace;
const V02_CICD_REPO = V02_LANE_SPEC.cicdRepo;
const DEV_NAMESPACE = "hwlab-dev";
const PROD_NAMESPACE = "hwlab-prod";
const CI_NAMESPACE = "hwlab-ci";
const ARGO_NAMESPACE = "argocd";
const DEV_APP = "hwlab-g14-dev";
const PROD_APP = "hwlab-g14-prod";
const V02_APP = V02_LANE_SPEC.app;
const V02_PIPELINE = V02_LANE_SPEC.pipeline;
const V02_POLLER = "hwlab-v02-branch-poller";
@@ -136,6 +138,15 @@ interface G14ControlPlaneOptions {
history: boolean;
}
interface G14LegacyRetirementOptions {
action: "status" | "plan" | "execute";
dryRun: boolean;
confirm: boolean;
wait: boolean;
timeoutSeconds: number;
reason: string;
}
type V02StatusTargetMode = "latest-source-head" | "source-commit" | "pipeline-run";
interface V02ControlPlaneStatusTarget {
@@ -414,6 +425,28 @@ function parseControlPlaneOptions(args: string[]): G14ControlPlaneOptions {
};
}
function parseLegacyRetirementOptions(args: string[]): G14LegacyRetirementOptions {
const [actionRaw = "status"] = args;
if (actionRaw !== "status" && actionRaw !== "plan" && actionRaw !== "execute") {
throw new Error("retirement usage: status|plan|execute [--dry-run|--confirm] [--wait] [--reason text] [--timeout-seconds N]");
}
const confirm = args.includes("--confirm");
const explicitDryRun = args.includes("--dry-run");
if (confirm && explicitDryRun) throw new Error("retirement accepts only one of --confirm or --dry-run");
if (confirm && actionRaw !== "execute") throw new Error("--confirm is only valid for retirement execute");
if (args.includes("--wait") && actionRaw !== "execute") throw new Error("--wait is only valid for retirement execute");
const reason = optionValue(args, "--reason") ?? "user requested immediate retirement of legacy G14 DEV/PROD";
if (reason.length > 240) throw new Error("--reason is limited to 240 characters");
return {
action: actionRaw,
confirm,
wait: args.includes("--wait"),
dryRun: actionRaw !== "execute" || explicitDryRun || !confirm,
timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", actionRaw === "execute" ? 60 : 30, 300),
reason,
};
}
function parseToolsImageOptions(args: string[]): G14ToolsImageOptions {
const [actionRaw] = args;
if (actionRaw !== "status" && actionRaw !== "build") {
@@ -5045,6 +5078,327 @@ function runG14Secret(options: G14SecretOptions): Record<string, unknown> {
};
}
function legacyG14RetirementStatePath(): string {
return rootPath(".state", "hwlab-g14", "legacy-g14-retirement.json");
}
function legacyG14RetirementApplications(): string[] {
return [DEV_APP, PROD_APP];
}
function legacyG14RetirementNamespaces(): string[] {
return [DEV_NAMESPACE, PROD_NAMESPACE];
}
function protectedG14RuntimeApplications(): string[] {
return [V02_APP, hwlabRuntimeLaneSpec("v03").app];
}
function protectedG14RuntimeNamespaces(): string[] {
return [V02_RUNTIME_NAMESPACE, hwlabRuntimeLaneSpec("v03").runtimeNamespace];
}
function readLegacyG14RetirementMarker(): Record<string, unknown> | null {
const path = legacyG14RetirementStatePath();
if (!existsSync(path)) return null;
try {
return record(JSON.parse(readFileSync(path, "utf8")) as unknown);
} catch (error) {
return { status: "unreadable", path, error: error instanceof Error ? error.message : String(error) };
}
}
function writeLegacyG14RetirementMarker(status: string, reason: string, details: Record<string, unknown> = {}): Record<string, unknown> {
const path = legacyG14RetirementStatePath();
mkdirSync(dirname(path), { recursive: true });
const previous = readLegacyG14RetirementMarker();
const marker = {
status,
reason,
requestedAt: typeof previous?.requestedAt === "string" ? previous.requestedAt : new Date().toISOString(),
updatedAt: new Date().toISOString(),
legacyApplications: legacyG14RetirementApplications(),
legacyNamespaces: legacyG14RetirementNamespaces(),
protectedApplications: protectedG14RuntimeApplications(),
protectedNamespaces: protectedG14RuntimeNamespaces(),
...details,
};
writeFileSync(path, `${JSON.stringify(marker, null, 2)}\n`, "utf8");
return { ...marker, path };
}
function legacyG14RetirementBlocksMonitor(): Record<string, unknown> | null {
const marker = readLegacyG14RetirementMarker();
if (marker === null) {
return {
status: "retired-by-contract",
path: legacyG14RetirementStatePath(),
reason: "legacy G14 DEV/PROD monitor is retired; inspect retirement status for live cluster evidence",
next: { status: "bun scripts/cli.ts hwlab g14 retirement status", v02Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02" },
};
}
const status = String(marker.status ?? "");
return status === "retiring" || status === "retired" || status === "failed" || status === "unreadable" ? marker : null;
}
function k8sMetadataNamespace(item: Record<string, unknown>): string | null {
const namespace = record(item.metadata).namespace;
return typeof namespace === "string" && namespace.length > 0 ? namespace : null;
}
function applicationSummary(item: Record<string, unknown>): Record<string, unknown> {
return {
name: k8sMetadataName(item),
namespace: k8sMetadataNamespace(item) ?? ARGO_NAMESPACE,
deletionTimestamp: record(item.metadata).deletionTimestamp ?? null,
sync: nested(item, ["status", "sync", "status"]) ?? null,
health: nested(item, ["status", "health", "status"]) ?? null,
revision: nested(item, ["status", "sync", "revision"]) ?? null,
};
}
function namespaceSummary(item: Record<string, unknown>): Record<string, unknown> {
return {
name: k8sMetadataName(item),
phase: nested(item, ["status", "phase"]) ?? null,
deletionTimestamp: record(item.metadata).deletionTimestamp ?? null,
};
}
function parseNamespacedResourcePreview(text: string): Record<string, unknown>[] {
const groups: Record<string, string[]> = {};
let current = "";
for (const line of text.split(/\r?\n/u)) {
const trimmed = line.trim();
if (trimmed.length === 0) continue;
const namespaceMatch = /^__namespace\t(.+)$/u.exec(trimmed);
if (namespaceMatch !== null) {
current = namespaceMatch[1] ?? "";
if (current.length > 0) groups[current] = [];
continue;
}
if (current.length > 0) groups[current]?.push(trimmed);
}
return Object.entries(groups).map(([namespace, items]) => ({
namespace,
count: items.length,
preview: items.slice(0, 40),
truncated: items.length > 40,
}));
}
function legacyG14RetirementStatusScript(): string {
const legacyApps = legacyG14RetirementApplications().map(shellQuote).join(" ");
const legacyNamespaces = legacyG14RetirementNamespaces().map(shellQuote).join(" ");
const protectedApps = protectedG14RuntimeApplications().map(shellQuote).join(" ");
const protectedNamespaces = protectedG14RuntimeNamespaces().map(shellQuote).join(" ");
return [
"set +e",
"section() {",
" name=\"$1\"",
" shift",
" printf '__UNIDESK_SECTION_BEGIN__ %s\\n' \"$name\"",
" \"$@\"",
" code=$?",
" printf '\\n__UNIDESK_SECTION_END__ %s exit=%s\\n' \"$name\" \"$code\"",
"}",
`section legacyApplications kubectl get application -n ${shellQuote(ARGO_NAMESPACE)} ${legacyApps} --ignore-not-found=true -o json`,
`section legacyNamespaces kubectl get namespace ${legacyNamespaces} --ignore-not-found=true -o json`,
`section protectedApplications kubectl get application -n ${shellQuote(ARGO_NAMESPACE)} ${protectedApps} --ignore-not-found=true -o json`,
`section protectedNamespaces kubectl get namespace ${protectedNamespaces} --ignore-not-found=true -o json`,
"section legacyResources sh -lc " + shellQuote([
`for ns in ${legacyNamespaces}; do`,
" printf '__namespace\\t%s\\n' \"$ns\"",
" kubectl get deploy,statefulset,svc,pod,pvc,ingress,configmap,secret -n \"$ns\" -o name --ignore-not-found=true 2>/dev/null | sed -n '1,160p'",
"done",
].join("\n")),
].join("\n");
}
function legacyG14RetirementStatus(): Record<string, unknown> {
const result = g14K3s(["script", "--", legacyG14RetirementStatusScript()], 60_000);
const sections = parseShellSections(statusText(result));
const legacyApplications = parseSectionJsonArray(sections.legacyApplications).map(applicationSummary);
const legacyNamespaces = parseSectionJsonArray(sections.legacyNamespaces).map(namespaceSummary);
const protectedApplications = parseSectionJsonArray(sections.protectedApplications).map(applicationSummary);
const protectedNamespaces = parseSectionJsonArray(sections.protectedNamespaces).map(namespaceSummary);
const terminating = legacyApplications.some((item) => item.deletionTimestamp !== null)
|| legacyNamespaces.some((item) => item.deletionTimestamp !== null || item.phase === "Terminating");
const retired = legacyApplications.length === 0 && legacyNamespaces.length === 0;
const retirementState = retired ? "retired" : terminating ? "terminating" : "active";
const protectedAppNames = new Set(protectedApplications.map((item) => item.name).filter((name): name is string => typeof name === "string"));
const protectedNamespaceNames = new Set(protectedNamespaces.map((item) => item.name).filter((name): name is string => typeof name === "string"));
const missingProtectedApplications = protectedG14RuntimeApplications().filter((name) => !protectedAppNames.has(name));
const missingProtectedNamespaces = protectedG14RuntimeNamespaces().filter((name) => !protectedNamespaceNames.has(name));
const sectionsOk =
shellSectionOk(sections.legacyApplications) &&
shellSectionOk(sections.legacyNamespaces) &&
shellSectionOk(sections.protectedApplications) &&
shellSectionOk(sections.protectedNamespaces) &&
shellSectionOk(sections.legacyResources);
return {
ok: isCommandSuccess(result) && sectionsOk,
command: "hwlab g14 retirement status",
provider: G14_PROVIDER,
argoNamespace: ARGO_NAMESPACE,
retirementState,
retired,
legacy: {
applications: legacyApplications,
namespaces: legacyNamespaces,
resources: parseNamespacedResourcePreview(String(sections.legacyResources?.stdout ?? "")),
},
protected: {
applications: protectedApplications,
namespaces: protectedNamespaces,
missingApplications: missingProtectedApplications,
missingNamespaces: missingProtectedNamespaces,
ok: missingProtectedApplications.length === 0 && missingProtectedNamespaces.length === 0,
},
marker: readLegacyG14RetirementMarker(),
result: compactCommandResult(result),
next: retired
? { verifyV02: "bun scripts/cli.ts hwlab g14 control-plane status --lane v02", verifyV03: "bun scripts/cli.ts hwlab nodes control-plane status --node G14 --lane v03" }
: { plan: "bun scripts/cli.ts hwlab g14 retirement plan", execute: "bun scripts/cli.ts hwlab g14 retirement execute --confirm" },
};
}
function legacyG14RetirementPlan(status = legacyG14RetirementStatus()): Record<string, unknown> {
return {
ok: status.ok === true,
command: "hwlab g14 retirement plan",
mode: "dry-run",
mutation: false,
reason: "retire legacy G14 DEV/PROD runtime and support surface without touching v0.2/v0.3",
destructiveTargets: {
applications: legacyG14RetirementApplications().map((name) => ({ apiVersion: "argoproj.io/v1alpha1", kind: "Application", namespace: ARGO_NAMESPACE, name })),
namespaces: legacyG14RetirementNamespaces().map((name) => ({ apiVersion: "v1", kind: "Namespace", name })),
},
protectedTargets: {
applications: protectedG14RuntimeApplications().map((name) => ({ apiVersion: "argoproj.io/v1alpha1", kind: "Application", namespace: ARGO_NAMESPACE, name })),
namespaces: protectedG14RuntimeNamespaces().map((name) => ({ apiVersion: "v1", kind: "Namespace", name })),
},
localSupportActions: [
{ action: "write-retirement-marker", path: legacyG14RetirementStatePath() },
{ action: "cancel-active-local-job", jobName: "hwlab_g14_pr_monitor" },
{ action: "block-new-monitor-starts", command: "bun scripts/cli.ts hwlab g14 monitor-prs --lane g14" },
],
status,
next: { execute: "bun scripts/cli.ts hwlab g14 retirement execute --confirm" },
};
}
function legacyG14RecordRolloutRetired(): Record<string, unknown> {
return {
ok: false,
command: "hwlab g14 record-rollout",
phase: "retired",
degradedReason: "legacy-g14-dev-prod-retired",
message: "Legacy G14 DEV rollout recording is retired. Use active runtime lane closeout/status evidence instead.",
retirement: readLegacyG14RetirementMarker(),
next: {
retirementStatus: "bun scripts/cli.ts hwlab g14 retirement status",
v02Status: "bun scripts/cli.ts hwlab g14 control-plane status --lane v02",
v03Status: "bun scripts/cli.ts hwlab nodes control-plane status --node G14 --lane v03",
},
};
}
function cancelLegacyG14MonitorJobs(dryRun: boolean): Record<string, unknown> {
const candidates = listJobs()
.filter((job) => job.name === "hwlab_g14_pr_monitor" && (job.status === "queued" || job.status === "running"))
.map((job) => ({ id: job.id, name: job.name, status: job.status, createdAt: job.createdAt, runnerPid: job.runnerPid ?? null }));
if (dryRun) return { ok: true, dryRun: true, candidates, actions: candidates.map((job) => ({ action: "would-cancel", ...job })) };
const actions = candidates.map((job) => cancelJob(job.id));
return { ok: actions.every((action) => record(action).ok !== false), dryRun: false, candidates, actions };
}
function legacyG14RetirementDeleteScript(): string {
const legacyApps = legacyG14RetirementApplications();
const legacyNamespaces = legacyG14RetirementNamespaces();
return [
"set +e",
"overall=0",
"printf '__UNIDESK_SECTION_BEGIN__ deleteApplications\\n'",
...legacyApps.map((app) => [
`kubectl delete application -n ${shellQuote(ARGO_NAMESPACE)} ${shellQuote(app)} --ignore-not-found=true --wait=false`,
"code=$?",
"[ \"$code\" -eq 0 ] || overall=1",
`printf 'application\\t%s\\texit=%s\\n' ${shellQuote(app)} "$code"`,
].join("\n")),
"printf '__UNIDESK_SECTION_END__ deleteApplications exit=0\\n'",
"printf '__UNIDESK_SECTION_BEGIN__ deleteNamespaces\\n'",
...legacyNamespaces.map((namespace) => [
`kubectl delete namespace ${shellQuote(namespace)} --ignore-not-found=true --wait=false`,
"code=$?",
"[ \"$code\" -eq 0 ] || overall=1",
`printf 'namespace\\t%s\\texit=%s\\n' ${shellQuote(namespace)} "$code"`,
].join("\n")),
"printf '__UNIDESK_SECTION_END__ deleteNamespaces exit=0\\n'",
"exit \"$overall\"",
].join("\n");
}
function waitForLegacyG14Retirement(timeoutSeconds: number): Record<string, unknown> {
const startedAtMs = Date.now();
let lastStatus: Record<string, unknown> = {};
let attempts = 0;
while (Date.now() - startedAtMs <= timeoutSeconds * 1000) {
attempts += 1;
lastStatus = legacyG14RetirementStatus();
if (lastStatus.retired === true) {
return { ok: true, attempts, elapsedMs: Date.now() - startedAtMs, status: lastStatus };
}
const wait = runCommand(["sleep", "5"], repoRoot, { timeoutMs: 7_000 });
if (wait.exitCode !== 0) break;
}
return { ok: false, attempts, elapsedMs: Date.now() - startedAtMs, status: lastStatus, degradedReason: "legacy-g14-retirement-not-complete-before-timeout" };
}
function runLegacyG14Retirement(options: G14LegacyRetirementOptions): Record<string, unknown> {
const before = legacyG14RetirementStatus();
if (options.action === "status") return before;
if (options.action === "plan" || options.dryRun) return { ...legacyG14RetirementPlan(before), command: `hwlab g14 retirement ${options.action}`, reason: options.reason };
const markerBeforeDelete = writeLegacyG14RetirementMarker("retiring", options.reason, { command: "hwlab g14 retirement execute --confirm" });
const localJobs = cancelLegacyG14MonitorJobs(false);
const deleteResult = g14K3s(["script", "--", legacyG14RetirementDeleteScript()], Math.max(60_000, options.timeoutSeconds * 1000));
const deleteSections = parseShellSections(statusText(deleteResult));
const afterDelete = legacyG14RetirementStatus();
const wait = options.wait ? waitForLegacyG14Retirement(options.timeoutSeconds) : null;
const finalStatus = wait === null ? afterDelete : record(wait.status);
const liveMutationOk = isCommandSuccess(deleteResult);
const finalRetired = finalStatus.retired === true;
const marker = writeLegacyG14RetirementMarker(finalRetired ? "retired" : liveMutationOk ? "retiring" : "failed", options.reason, {
command: "hwlab g14 retirement execute --confirm",
deleteExitCode: deleteResult.exitCode,
finalRetirementState: finalStatus.retirementState ?? null,
});
return {
ok: liveMutationOk && (options.wait ? finalRetired : finalStatus.retirementState !== "active"),
command: "hwlab g14 retirement execute",
mode: "confirmed-retirement",
mutation: true,
reason: options.reason,
before,
markerBeforeDelete,
localJobs,
delete: {
ok: liveMutationOk,
applications: String(deleteSections.deleteApplications?.stdout ?? "").split(/\r?\n/u).filter(Boolean),
namespaces: String(deleteSections.deleteNamespaces?.stdout ?? "").split(/\r?\n/u).filter(Boolean),
result: compactCommandResult(deleteResult),
},
afterDelete,
wait,
finalStatus,
marker,
next: finalRetired
? { verifyV02: "bun scripts/cli.ts hwlab g14 control-plane status --lane v02", verifyV03: "bun scripts/cli.ts hwlab nodes control-plane status --node G14 --lane v03" }
: { status: "bun scripts/cli.ts hwlab g14 retirement status" },
};
}
function deleteLegacyGitMirrorCronJob(dryRun: boolean): CommandJsonResult {
return g14K3s([
"kubectl",
@@ -7991,14 +8345,14 @@ function newDailyBriefBody(date: string): string {
"",
"## 常驻观察与长期建议",
"",
"- G14 HWLAB source truth 固定为 G14 `/root/hwlab` 与 `origin/G14`DEV rollout 只接受 G14 Tekton/GitOps/Argo 和公网 health 证据。",
"- Legacy G14 DEV rollout 已退役;当前 HWLAB runtime 证据必须来自 active runtime lane。",
"- GitHub issue/PR 写操作必须走 UniDesk `gh` 子命令;G14 k3s 操作必须走 UniDesk `trans G14:k3s`。",
].join("\n");
}
function insertBriefIndexRow(indexBody: string, date: string, brief: { number: number; url: string }): string {
if (parseBriefIssueFromIndex(indexBody, date) !== null) return indexBody;
const row = `| ${date} | [#${brief.number}](${brief.url}) | 已创建当日 G14 DEV rollout 自动简报入口;后续每次 DEV 上线记录 CI/CD 耗时和上线 changelog。 | 继续监控 HWLAB base=G14 PRready 后自动合并并滚动到 G14 DEV。 |`;
const row = `| ${date} | [#${brief.number}](${brief.url}) | legacy G14 DEV rollout 自动简报入口已退役;当前证据写入 active runtime lane closeout。 | 使用 active runtime lane 的受控 CI/CD 入口和 issue 评论记录。 |`;
const marker = "### 指挥简报索引";
const markerIndex = indexBody.indexOf(marker);
if (markerIndex === -1) return `${indexBody.trimEnd()}\n\n${marker}\n\n| 日期(北京时间) | 指挥简报 issue | 当日推进 | 下一步计划 |\n| --- | --- | --- | --- |\n${row}\n`;
@@ -8131,7 +8485,7 @@ export function rolloutRecordBody(input: {
"",
`## 更新 ${now.date} ${now.time.slice(0, 5)} 北京时间`,
"",
`### G14 DEV rolloutPR #${input.pr.number}`,
`### Retired legacy G14 DEV rollout recordPR #${input.pr.number}`,
"",
`- PR: [#${input.pr.number} ${title}](${url})`,
`- 合并 commit: \`${input.sourceCommit}\``,
@@ -8827,6 +9181,17 @@ async function monitorV02Cycle(options: G14MonitorOptions, cycle: number): Promi
async function monitorCycle(options: G14MonitorOptions, cycle: number): Promise<Record<string, unknown>> {
if (options.lane === "v02") return monitorV02Cycle(options, cycle);
const retirement = legacyG14RetirementBlocksMonitor();
if (retirement !== null) {
return {
ok: false,
cycle,
phase: "retired",
degradedReason: "legacy-g14-dev-prod-retired",
retirement,
next: { status: "bun scripts/cli.ts hwlab g14 retirement status", v02Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02" },
};
}
printEvent("g14.monitor.cycle.start", { cycle, dryRun: options.dryRun });
const precheck = precheckWorkspace();
if (!isCommandSuccess(precheck)) return { ok: false, cycle, phase: "workspace-precheck", precheck };
@@ -8881,12 +9246,12 @@ export function hwlabG14Help(): Record<string, unknown> {
command: "hwlab g14",
output: "json",
usage: [
"bun scripts/cli.ts hwlab g14 monitor-prs",
"bun scripts/cli.ts hwlab g14 monitor-prs --lane v02",
"bun scripts/cli.ts hwlab g14 monitor-prs --lane v02 --status",
"bun scripts/cli.ts hwlab g14 monitor-prs --once --dry-run",
"bun scripts/cli.ts hwlab g14 retirement status",
"bun scripts/cli.ts hwlab g14 retirement plan",
"bun scripts/cli.ts hwlab g14 retirement execute --confirm",
"bun scripts/cli.ts hwlab g14 monitor-prs --lane v02 --once --dry-run",
"bun scripts/cli.ts hwlab g14 record-rollout --pr <number> [--source-commit sha]",
"bun scripts/cli.ts hwlab g14 control-plane status --lane v02",
"bun scripts/cli.ts hwlab g14 control-plane status --lane v02 --history",
"bun scripts/cli.ts hwlab g14 control-plane status --lane v02 --pipeline-run hwlab-v02-ci-poll-<short-sha>",
@@ -8955,20 +9320,22 @@ export function hwlabG14Help(): Record<string, unknown> {
"bun scripts/cli.ts hwlab g14 upstream-image ensure --name openfga --tag v1.17.0 --confirm",
"bun scripts/cli.ts job status <jobId> --tail-bytes 30000",
],
description: "G14 HWLAB PR monitor, DEV rollout command, bounded v0.2 control-plane bootstrap/cleanup/runtime-migration helper, node-scoped runtime lane v03 control-plane apply/status/refresh/trigger entry, runtime lane SecretRef bootstrap, devops-infra git mirror and observability maintenance, controlled CI tools image build/status entry, and allowlisted upstream image mirroring. The public monitor starts a fire-and-forget job. Default monitor lane is base=G14; --lane v02 monitors base=v0.2 PRs, waits for GitHub preflight/CI readiness, automatically merges ready PRs without waiting for other active v0.2 PipelineRuns, triggers v0.2 CD with latest-only GitOps writeback, flushes the git mirror when needed, and posts deduplicated PR comments for pending, blocked/conflict, success, superseded, failure, or timeout states. confirmed control-plane trigger-current and git-mirror sync/flush also return async jobs by default, with --wait reserved for explicit synchronous debugging. control-plane v02 keeps the full closeout/cleanup/runtime-migration verdict path; v03+ is advertised through `hwlab nodes ... --node <node-id> --lane vNN` so node identity remains configuration data instead of a command family. secret status/ensure is the standard runtime lane SecretRef bootstrap path for OpenFGA and master admin API key; it never reads or prints secret values. upstream-image status/ensure only mirrors allowlisted upstream runtime images into the G14 local registry. git-mirror status/apply/sync/flush is the manual devops-infra mirror/relay control path and does not install a CronJob. observability status/apply/query/targets/boundary/closeout owns the shared Prometheus Operator and Prometheus instance in devops-infra, adds bounded PromQL assertions and semantic closeout summaries, while HWLAB lane manifests own only ServiceMonitor and PrometheusRule objects.",
description: "G14 HWLAB PR monitor, legacy DEV/PROD retirement status/plan/execute command, bounded v0.2 control-plane bootstrap/cleanup/runtime-migration helper, node-scoped runtime lane v03 control-plane apply/status/refresh/trigger entry, runtime lane SecretRef bootstrap, devops-infra git mirror and observability maintenance, controlled CI tools image build/status entry, and allowlisted upstream image mirroring. The legacy base=G14 monitor is blocked by the retirement contract; the local retirement marker records live execution evidence. `--lane v02` monitors base=v0.2 PRs, waits for GitHub preflight/CI readiness, automatically merges ready PRs without waiting for other active v0.2 PipelineRuns, triggers v0.2 CD with latest-only GitOps writeback, flushes the git mirror when needed, and posts deduplicated PR comments for pending, blocked/conflict, success, superseded, failure, or timeout states. confirmed control-plane trigger-current and git-mirror sync/flush also return async jobs by default, with --wait reserved for explicit synchronous debugging. control-plane v02 keeps the full closeout/cleanup/runtime-migration verdict path; v03+ is advertised through `hwlab nodes ... --node <node-id> --lane vNN` so node identity remains configuration data instead of a command family. secret status/ensure is the standard runtime lane SecretRef bootstrap path for OpenFGA and master admin API key; it never reads or prints secret values. upstream-image status/ensure only mirrors allowlisted upstream runtime images into the G14 local registry. git-mirror status/apply/sync/flush is the manual devops-infra mirror/relay control path and does not install a CronJob. observability status/apply/query/targets/boundary/closeout owns the shared Prometheus Operator and Prometheus instance in devops-infra, adds bounded PromQL assertions and semantic closeout summaries, while HWLAB lane manifests own only ServiceMonitor and PrometheusRule objects.",
defaults: {
repo: HWLAB_REPO,
base: G14_SOURCE_BRANCH,
legacyBase: G14_SOURCE_BRANCH,
v02Base: V02_SOURCE_BRANCH,
runtimeLaneConfig: hwlabRuntimeLaneConfigPath(),
runtimeLanes: hwlabRuntimeLaneIds(),
provider: G14_PROVIDER,
workspace: G14_WORKSPACE,
legacyWorkspace: G14_WORKSPACE,
v02Workspace: V02_WORKSPACE,
v03Workspace: hwlabRuntimeLaneSpec("v03").workspace,
ciToolsImageRepo: G14_CI_TOOLS_IMAGE_REPO,
intervalSeconds: DEFAULT_INTERVAL_SECONDS,
devApplication: DEV_APP,
prodApplication: PROD_APP,
legacyRetirementState: legacyG14RetirementStatePath(),
v02Application: V02_APP,
v03Application: hwlabRuntimeLaneSpec("v03").app,
v03Node: hwlabRuntimeLaneSpec("v03").nodeId,
@@ -8999,6 +9366,7 @@ function monitorStatus(options: G14MonitorOptions): Record<string, unknown> {
const stateFileName = hwlabG14MonitorStateFileName(options);
const stateFileRole = hwlabG14MonitorStateRole(options);
const latestPath = join(stateDir, stateFileName);
const retirement = options.lane === "g14" ? legacyG14RetirementBlocksMonitor() : null;
const exists = existsSync(latestPath);
let latest: Record<string, unknown> | null = null;
let job: Record<string, unknown> | null = null;
@@ -9047,7 +9415,11 @@ function monitorStatus(options: G14MonitorOptions): Record<string, unknown> {
job,
statusCommand,
degradedReason,
next: statusCommand === null ? {
retirement,
next: retirement !== null ? {
retirementStatus: "bun scripts/cli.ts hwlab g14 retirement status",
v02Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02",
} : statusCommand === null ? {
start: `bun scripts/cli.ts hwlab g14 monitor-prs --lane ${options.lane}`,
} : {
status: statusCommand,
@@ -9059,8 +9431,7 @@ export async function runHwlabG14Command(_config: Config, args: string[]): Promi
if (args.length === 0 || args.includes("--help") || args.includes("-h")) return { ok: true, ...hwlabG14Help() };
const [action] = args;
if (action === "record-rollout") {
const options = parseRecordRolloutOptions(args.slice(1));
return appendRolloutBrief(options);
return legacyG14RecordRolloutRetired();
}
if (action === "control-plane") {
const options = parseControlPlaneOptions(args.slice(1));
@@ -9072,6 +9443,10 @@ export async function runHwlabG14Command(_config: Config, args: string[]): Promi
}
return runV02ControlPlane(options);
}
if (action === "retirement") {
const options = parseLegacyRetirementOptions(args.slice(1));
return runLegacyG14Retirement(options);
}
if (action === "secret") {
const options = parseSecretOptions(args.slice(1));
return runG14Secret(options);
@@ -9096,11 +9471,27 @@ export async function runHwlabG14Command(_config: Config, args: string[]): Promi
return runG14Observability(options);
}
if (action !== "monitor-prs") {
return { ok: false, command: `hwlab g14 ${action ?? ""}`.trim(), degradedReason: "unsupported-command", message: "supported commands: hwlab g14 monitor-prs, hwlab g14 record-rollout, hwlab g14 control-plane, hwlab g14 secret, hwlab g14 git-mirror, hwlab g14 observability, hwlab g14 tools-image, hwlab g14 upstream-image" };
return { ok: false, command: `hwlab g14 ${action ?? ""}`.trim(), degradedReason: "unsupported-command", message: "supported commands: hwlab g14 monitor-prs, hwlab g14 retirement, hwlab g14 control-plane, hwlab g14 secret, hwlab g14 git-mirror, hwlab g14 observability, hwlab g14 tools-image, hwlab g14 upstream-image" };
}
const options = parseOptions(args.slice(1));
if (options.worker) return runMonitorWorker(options);
if (args.includes("--status")) return monitorStatus(options);
const retirement = options.lane === "g14" ? legacyG14RetirementBlocksMonitor() : null;
if (retirement !== null) {
return {
ok: false,
command: "hwlab g14 monitor-prs",
lane: options.lane,
baseBranch: monitorBaseBranch(options.lane),
degradedReason: "legacy-g14-dev-prod-retired",
message: "Legacy G14 DEV/PROD has a retirement marker; start v0.2 monitor with --lane v02 or inspect retirement status.",
retirement,
next: {
retirementStatus: "bun scripts/cli.ts hwlab g14 retirement status",
v02Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02",
},
};
}
const command = ["bun", "scripts/cli.ts", "hwlab", "g14", "monitor-prs", "--worker", "--lane", options.lane, "--interval-seconds", String(options.intervalSeconds), "--timeout-seconds", String(options.timeoutSeconds), ...(options.once ? ["--once"] : []), ...(options.dryRun ? ["--dry-run"] : []), ...(options.maxCycles > 0 ? ["--max-cycles", String(options.maxCycles)] : [])];
const jobName = options.lane === "v02" ? "hwlab_g14_v02_pr_monitor" : "hwlab_g14_pr_monitor";
const jobNote = options.lane === "v02"