diff --git a/AGENTS.md b/AGENTS.md index d1942b6f..c8a8ad78 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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/PR;v0.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/PR;v0.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 truth;master 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 truth;master 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 truth,CI 产出 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 contract,runner 无 `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.2,CI/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 e2e;catalog/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 `:旧 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 ]` / `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 和性能面板的可观测性规则。 diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 1e199dad..9e282dc1 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -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 kubectl|logs|get|describe` / `tran ...` 仍是 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 '' --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 PR,ready 时合并,然后通过 UniDesk `trans G14:k3s` 观察 `hwlab-g14-ci-poll-`、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 --source-commit ` 手动补记,手动补记同样会按 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 `,必要时执行 `git-mirror flush --confirm --wait`。v0.2 CD 采用 latest-only:旧 PipelineRun 不取消、不等待,但 promotion 写 `v0.2-gitops` 前必须重新确认 source head,stale 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-` 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 PVC;dry-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 命令彼此独立。 diff --git a/docs/reference/g14.md b/docs/reference/g14.md index 0201677f..b665f89d 100644 --- a/docs/reference/g14.md +++ b/docs/reference/g14.md @@ -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. diff --git a/docs/reference/hwlab.md b/docs/reference/hwlab.md index c7b09614..adf344f7 100644 --- a/docs/reference/hwlab.md +++ b/docs/reference/hwlab.md @@ -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 state;G14 侧 `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-`;修改后只重启 `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-`;修改后只重启 `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` 是人工开发和短连接源码工具 workspace,dirty/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` 是人工开发和短连接源码工具 workspace,dirty/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=`、`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` ready;D601 Windows 能访问 `http://74.48.78.17:17667/health/live`;D601 `hwlab-gateway` 注册后 cloud-api 能看到 gateway session/resource/capability;G14 `device-agent-71-freq` `/health` 和 `/skills` 返回 ready;通过 device-agent 发起一条只读或 `echo` 级 gateway shell operation,并返回 operation/evidence/trace 摘要。 +- 最小验收顺序是:active G14 runtime lane cloud-api `/health/live` ready;D601 Windows 能访问该 lane 的 public `/health/live`;D601 `hwlab-gateway` 注册后 cloud-api 能看到 gateway session/resource/capability;G14 `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 部署副本: diff --git a/scripts/hwlab-g14-contract-test.ts b/scripts/hwlab-g14-contract-test.ts index 1cf4baf5..85ca1fc9 100644 --- a/scripts/hwlab-g14-contract-test.ts +++ b/scripts/hwlab-g14-contract-test.ts @@ -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")) diff --git a/scripts/src/hwlab-g14.ts b/scripts/src/hwlab-g14.ts index 75782e7c..6ca02638 100644 --- a/scripts/src/hwlab-g14.ts +++ b/scripts/src/hwlab-g14.ts @@ -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 { }; } +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 | 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 = {}): Record { + 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 | 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 | null { + const namespace = record(item.metadata).namespace; + return typeof namespace === "string" && namespace.length > 0 ? namespace : null; +} + +function applicationSummary(item: Record): Record { + 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): Record { + return { + name: k8sMetadataName(item), + phase: nested(item, ["status", "phase"]) ?? null, + deletionTimestamp: record(item.metadata).deletionTimestamp ?? null, + }; +} + +function parseNamespacedResourcePreview(text: string): Record[] { + const groups: Record = {}; + 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 { + 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 { + 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 { + 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 { + 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 { + const startedAtMs = Date.now(); + let lastStatus: Record = {}; + 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 { + 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 PR,ready 后自动合并并滚动到 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 rollout:PR #${input.pr.number}`, + `### Retired legacy G14 DEV rollout record:PR #${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> { 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 { 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 [--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-", @@ -8955,20 +9320,22 @@ export function hwlabG14Help(): Record { "bun scripts/cli.ts hwlab g14 upstream-image ensure --name openfga --tag v1.17.0 --confirm", "bun scripts/cli.ts job status --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 --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 --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 { 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 | null = null; let job: Record | null = null; @@ -9047,7 +9415,11 @@ function monitorStatus(options: G14MonitorOptions): Record { 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"