Merge pull request #500 from pikasTech/fix/g14-gitops-branch-state-20260527
fix: keep G14 generated state on gitops branch
This commit is contained in:
@@ -24,6 +24,11 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥
|
||||
- 当前发布只走 G14 k3s Tekton + `G14-gitops` + Argo CD;镜像构建 helper 是 `scripts/g14-artifact-publish.mjs`,desired state 由 `scripts/g14-gitops-render.mjs` 生成,细则见 [docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)。
|
||||
- 发现旧脚本、旧文档或旧 `hwlab-cli cicd` 再次进入 G14 Pipeline、AGENTS 或长期参考时,优先删除旧入口并把调用方改到 G14 GitOps 路径,不再做兼容保留。
|
||||
|
||||
## P0 门禁最小化纪律
|
||||
|
||||
- 架构迁移时,过时的自检、预检、guard、gate 优先拆除;不要在旧门禁上继续加例外、加复杂度或制造噪声。
|
||||
- 新增自检、预检、guard、gate 必须遵循最小原则,只覆盖明确高价值风险;禁止乱加门禁导致系统僵化、难迁移、难调试或产生大量误报和摩擦。
|
||||
|
||||
## 工作区
|
||||
|
||||
- G14 分支固定 source workspace 是 G14 节点上的 `/root/hwlab`,固定使用 `G14` 分支和 `origin git@github.com:pikasTech/HWLAB.git`。在 G14 上进行代码、文档、GitOps render、Tekton/poller/Argo CD 修复或 CI/CD 验证前,必须先确认 `pwd` 为 `/root/hwlab` 且 `git status --short --branch` 为 `G14...origin/G14`;不满足时先停止并修正 workspace。
|
||||
@@ -77,7 +82,7 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥
|
||||
- Cloud Workbench 布局/遮挡 smoke:`npm run web:layout`;local-build 用 `npm run web:layout:build`;DEV deploy 后用 `npm run web:layout:live`。
|
||||
- G14 artifact build helper:`node scripts/g14-artifact-publish.mjs --publish ...`,只能由 G14 Tekton task 携带 CI artifact identity 调用;人工发布走 G14 poller/GitOps,不走 legacy CLI CD。
|
||||
- G14 monorepo 组件计划:`node scripts/g14-ci-plan.mjs --base-ref <ref> --target-ref <ref> --pretty`,只读分析 affected/reused services,基于内建 service-path component model 与 `deploy/deploy.json`;细则见 [docs/reference/g14-gitops-cicd.md](docs/reference/g14-gitops-cicd.md)。
|
||||
- G14 GitOps 渲染:`npm run g14:gitops:render`;检查已生成 Tekton/Argo CD manifests:`npm run g14:gitops:check`
|
||||
- G14 GitOps 渲染:`npm run g14:gitops:render`;source 分支不再运行生成物 drift check,发布态由 Tekton 写入 `G14-gitops`。
|
||||
- DEV 依赖 runtime base 构建:`npm run dev-runtime-base:build`
|
||||
- Legacy D601 DEV CD:旧脚本入口已删除;事故回放只读历史 issue/commit,不恢复旧命令。
|
||||
- CI/CD job 查询:`node tools/hwlab-cli/bin/hwlab-cli.mjs cicd status|logs|report <jobId>`
|
||||
|
||||
+8
-6
@@ -70,17 +70,19 @@ node scripts/deploy-desired-state-plan.mjs --promotion-commit <sha> --check
|
||||
The desired-state plan reads `deploy/deploy.json`,
|
||||
`deploy/artifact-catalog.dev.json`, `deploy/k8s/base/workloads.yaml`, and the
|
||||
optional artifact report snapshot. `deploy/deploy.json` is the human-authored
|
||||
runtime config truth source. Generated image identity lives in
|
||||
`deploy/artifact-catalog.dev.json`; GitOps render overlays catalog image fields
|
||||
and generated env mirrors such as `HWLAB_COMMIT_ID`, `HWLAB_IMAGE`,
|
||||
runtime config truth source. Published image identity lives in
|
||||
`G14-gitops:deploy/artifact-catalog.dev.json`; the source branch catalog is only
|
||||
a seed contract for local checks. GitOps render overlays catalog image fields and
|
||||
generated env mirrors such as `HWLAB_COMMIT_ID`, `HWLAB_IMAGE`,
|
||||
`HWLAB_IMAGE_TAG`, and `HWLAB_SKILLS_COMMIT_ID` into runtime manifests.
|
||||
|
||||
The plan blocks if generated artifact identity leaks back into `deploy.json`,
|
||||
or if `hwlab-cloud-api` loses the Code Agent provider Secret ref
|
||||
`hwlab-code-agent-provider/openai-api-key`, the `OPENAI_API_KEY` env name, or
|
||||
the DEV egress proxy base-url env. Promotion updates must refresh only
|
||||
`deploy/artifact-catalog.dev.json` from a publish report before render/check;
|
||||
report files are contextual evidence only and must not override source truth.
|
||||
the DEV egress proxy base-url env. Promotion updates must refresh only the
|
||||
workspace catalog from a publish report before render; Tekton writes the
|
||||
refreshed catalog to `G14-gitops`, not back to the source branch. Report files
|
||||
are contextual evidence only and must not override human-authored source truth.
|
||||
|
||||
This is source/dry-run support only. It does not prove a registry image exists,
|
||||
does not build, pull, push, apply, restart, or touch PROD, and must not be used
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"endpoint": "http://74.48.78.17:16667",
|
||||
"commitId": "8bdcff5",
|
||||
"artifactState": "published",
|
||||
"commitId": "278bbe1",
|
||||
"artifactState": "contract-skeleton",
|
||||
"publish": {
|
||||
"ciPublished": true,
|
||||
"registryVerified": true,
|
||||
"provenance": "g14-ci-artifact:hwlab-g14-ci-poll-8bdcff56c580-collect",
|
||||
"note": "Digest fields were copied from a successful DEV artifact publish report for this source commit."
|
||||
"ciPublished": false,
|
||||
"registryVerified": false,
|
||||
"provenance": "not_available_until_publish",
|
||||
"note": "Artifact identity was refreshed to this source commit, but no publish report proved registry digests."
|
||||
},
|
||||
"healthContract": {
|
||||
"method": "GET",
|
||||
@@ -66,11 +66,11 @@
|
||||
"services": [
|
||||
{
|
||||
"serviceId": "hwlab-cloud-api",
|
||||
"commitId": "af46386",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:af46386",
|
||||
"imageTag": "af46386",
|
||||
"digest": "sha256:fe724e72e7cea3c93abe17085c4d885c78d34d18eb2a3c01f987d1971e2f4425",
|
||||
"publishState": "reused",
|
||||
"commitId": "278bbe1",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-api:278bbe1",
|
||||
"imageTag": "278bbe1",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
@@ -78,8 +78,8 @@
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": null,
|
||||
"buildCreatedAt": "2026-05-27T00:14:43.480Z",
|
||||
"notPublishedReason": "publish_not_run",
|
||||
"buildCreatedAt": null,
|
||||
"buildSource": null,
|
||||
"componentCommitId": "8d533cb736c3bf30fe97ef0ddf3d24047cad1b78",
|
||||
"componentInputHash": "7498160c36db86db76d3c1b103e397f2b2304248136d3b79eafe1ab69d4abeff",
|
||||
@@ -101,11 +101,11 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-cloud-web",
|
||||
"commitId": "af46386",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:af46386",
|
||||
"imageTag": "af46386",
|
||||
"digest": "sha256:3f5d4dcd1df4f736293d4223acb8104fd857e847a3a9a0acec4ef031ea333913",
|
||||
"publishState": "reused",
|
||||
"commitId": "278bbe1",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cloud-web:278bbe1",
|
||||
"imageTag": "278bbe1",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
@@ -113,8 +113,8 @@
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": null,
|
||||
"buildCreatedAt": "2026-05-27T00:14:45.158Z",
|
||||
"notPublishedReason": "publish_not_run",
|
||||
"buildCreatedAt": null,
|
||||
"buildSource": null,
|
||||
"componentCommitId": "8d533cb736c3bf30fe97ef0ddf3d24047cad1b78",
|
||||
"componentInputHash": "fdd27830a0cef173fc07a2cab8cd469d67aaff9da12bb0195f188e323854f93a",
|
||||
@@ -136,11 +136,11 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-mgr",
|
||||
"commitId": "af46386",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:af46386",
|
||||
"imageTag": "af46386",
|
||||
"digest": "sha256:5a55d6199b5bf2db4afa55a0be8cc6eae38f2dcac8a7b374ebbec2af2f36d71c",
|
||||
"publishState": "reused",
|
||||
"commitId": "278bbe1",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-mgr:278bbe1",
|
||||
"imageTag": "278bbe1",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
@@ -148,8 +148,8 @@
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": null,
|
||||
"buildCreatedAt": "2026-05-27T00:14:44.484Z",
|
||||
"notPublishedReason": "publish_not_run",
|
||||
"buildCreatedAt": null,
|
||||
"buildSource": null,
|
||||
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
|
||||
"componentInputHash": "ce4aa5c0b74c873d3270a71d9481a8255571b574f024bd5980f18547b4361860",
|
||||
@@ -171,11 +171,11 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-worker",
|
||||
"commitId": "af46386",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:af46386",
|
||||
"imageTag": "af46386",
|
||||
"digest": "sha256:03f09333657bb37ad059004aabb4484503f79866ba19272d5af9bf611bbb9c5d",
|
||||
"publishState": "reused",
|
||||
"commitId": "278bbe1",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-worker:278bbe1",
|
||||
"imageTag": "278bbe1",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
@@ -183,8 +183,8 @@
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": null,
|
||||
"buildCreatedAt": "2026-05-27T00:14:45.112Z",
|
||||
"notPublishedReason": "publish_not_run",
|
||||
"buildCreatedAt": null,
|
||||
"buildSource": null,
|
||||
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
|
||||
"componentInputHash": "7b426c226ec6f6d4de516f58055f3bf26d911477a88ebeef320066b1903ef40f",
|
||||
@@ -206,11 +206,11 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-gateway",
|
||||
"commitId": "af46386",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:af46386",
|
||||
"imageTag": "af46386",
|
||||
"digest": "sha256:1f44666c01f11416fee24b2d671c2ddcea3b5a6c575fe16ad0cf5307315d35e6",
|
||||
"publishState": "reused",
|
||||
"commitId": "278bbe1",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway:278bbe1",
|
||||
"imageTag": "278bbe1",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
@@ -218,8 +218,8 @@
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": null,
|
||||
"buildCreatedAt": "2026-05-27T00:14:45.140Z",
|
||||
"notPublishedReason": "publish_not_run",
|
||||
"buildCreatedAt": null,
|
||||
"buildSource": null,
|
||||
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
|
||||
"componentInputHash": "6c97b4f8e49ba46148dde9126a21f475356bfdd4d6826326799d03d68212002e",
|
||||
@@ -241,11 +241,11 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-gateway-simu",
|
||||
"commitId": "af46386",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:af46386",
|
||||
"imageTag": "af46386",
|
||||
"digest": "sha256:9b65bfff46f5fa206e7887340b9e7df2647eb4d0c186f9ad9796636551cd121b",
|
||||
"publishState": "reused",
|
||||
"commitId": "278bbe1",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-gateway-simu:278bbe1",
|
||||
"imageTag": "278bbe1",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
@@ -253,8 +253,8 @@
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": null,
|
||||
"buildCreatedAt": "2026-05-27T00:14:45.127Z",
|
||||
"notPublishedReason": "publish_not_run",
|
||||
"buildCreatedAt": null,
|
||||
"buildSource": null,
|
||||
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
|
||||
"componentInputHash": "184706b960dc00c23d6aacf35bde93558d422329635752cfb85796c6fe5e4820",
|
||||
@@ -276,11 +276,11 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-box-simu",
|
||||
"commitId": "af46386",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:af46386",
|
||||
"imageTag": "af46386",
|
||||
"digest": "sha256:909fd838bf6fa7dc2b2eb7b2a868bde4d110aab1640272ee63dbcf6ae488b330",
|
||||
"publishState": "reused",
|
||||
"commitId": "278bbe1",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-box-simu:278bbe1",
|
||||
"imageTag": "278bbe1",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
@@ -288,8 +288,8 @@
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": null,
|
||||
"buildCreatedAt": "2026-05-27T00:14:45.148Z",
|
||||
"notPublishedReason": "publish_not_run",
|
||||
"buildCreatedAt": null,
|
||||
"buildSource": null,
|
||||
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
|
||||
"componentInputHash": "1dfe5c35338d049b996a27f628d8d7b64ddacfa51de07e4c1cbd4a0f19c289bf",
|
||||
@@ -311,11 +311,11 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-patch-panel",
|
||||
"commitId": "af46386",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:af46386",
|
||||
"imageTag": "af46386",
|
||||
"digest": "sha256:5d49ba34c8dc1e71eb5fedc769ea0759b4ab66d7665086fc773c2020fcd11a2b",
|
||||
"publishState": "reused",
|
||||
"commitId": "278bbe1",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-patch-panel:278bbe1",
|
||||
"imageTag": "278bbe1",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
@@ -323,8 +323,8 @@
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": null,
|
||||
"buildCreatedAt": "2026-05-27T00:14:48.434Z",
|
||||
"notPublishedReason": "publish_not_run",
|
||||
"buildCreatedAt": null,
|
||||
"buildSource": null,
|
||||
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
|
||||
"componentInputHash": "4b84d74569d7d847c886695014aa84236c678b19ceeaa7df1f80ff7aa06c00ed",
|
||||
@@ -346,11 +346,11 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-router",
|
||||
"commitId": "af46386",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-router:af46386",
|
||||
"imageTag": "af46386",
|
||||
"digest": "sha256:074539cf5a9aef2330c6dc8df96d0be76f5fbb940f8d0ad20f1d67cb1f299666",
|
||||
"publishState": "reused",
|
||||
"commitId": "278bbe1",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-router:278bbe1",
|
||||
"imageTag": "278bbe1",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
@@ -358,8 +358,8 @@
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": null,
|
||||
"buildCreatedAt": "2026-05-27T00:14:48.571Z",
|
||||
"notPublishedReason": "publish_not_run",
|
||||
"buildCreatedAt": null,
|
||||
"buildSource": null,
|
||||
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
|
||||
"componentInputHash": "a2e72fca1d0acb7b5db4ecf77ff5ffacae7e897e8d50920cae8b17fcb6e1e6fb",
|
||||
@@ -381,11 +381,11 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-tunnel-client",
|
||||
"commitId": "af46386",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:af46386",
|
||||
"imageTag": "af46386",
|
||||
"digest": "sha256:f1f200009731ba832dc26258b01c3abb4de711237728a95124822e0d056c70fe",
|
||||
"publishState": "reused",
|
||||
"commitId": "278bbe1",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-tunnel-client:278bbe1",
|
||||
"imageTag": "278bbe1",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
@@ -393,8 +393,8 @@
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": null,
|
||||
"buildCreatedAt": "2026-05-27T00:14:48.544Z",
|
||||
"notPublishedReason": "publish_not_run",
|
||||
"buildCreatedAt": null,
|
||||
"buildSource": null,
|
||||
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
|
||||
"componentInputHash": "58f4fc6f1b56c6074cdeae1d33bfe22f54072ef0d45351d3186cdcf15a3f1573",
|
||||
@@ -416,11 +416,11 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-edge-proxy",
|
||||
"commitId": "af46386",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:af46386",
|
||||
"imageTag": "af46386",
|
||||
"digest": "sha256:b9dbf6f08bfea10ec9ca9c15cd864e96eb37e483fcd8716513dd74533d8b3cf0",
|
||||
"publishState": "reused",
|
||||
"commitId": "278bbe1",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-edge-proxy:278bbe1",
|
||||
"imageTag": "278bbe1",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
@@ -428,8 +428,8 @@
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": null,
|
||||
"buildCreatedAt": "2026-05-27T00:14:48.521Z",
|
||||
"notPublishedReason": "publish_not_run",
|
||||
"buildCreatedAt": null,
|
||||
"buildSource": null,
|
||||
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
|
||||
"componentInputHash": "497d88f5f34075f2ba064cdc10d3a80a236e96e4eab4ca71ae3138a751201315",
|
||||
@@ -451,11 +451,11 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-cli",
|
||||
"commitId": "af46386",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cli:af46386",
|
||||
"imageTag": "af46386",
|
||||
"digest": "sha256:3908855f68043119452bd40109668c7a163c33bf048ddcf0cc9c1097ef741c5a",
|
||||
"publishState": "reused",
|
||||
"commitId": "278bbe1",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-cli:278bbe1",
|
||||
"imageTag": "278bbe1",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
@@ -463,8 +463,8 @@
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": null,
|
||||
"buildCreatedAt": "2026-05-27T00:14:48.479Z",
|
||||
"notPublishedReason": "publish_not_run",
|
||||
"buildCreatedAt": null,
|
||||
"buildSource": null,
|
||||
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
|
||||
"componentInputHash": "bff91d0ad2c23aceacc494cd4ad84e93d163c9bcae8815ac4018f2a0f37a0617",
|
||||
@@ -486,11 +486,11 @@
|
||||
},
|
||||
{
|
||||
"serviceId": "hwlab-agent-skills",
|
||||
"commitId": "af46386",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:af46386",
|
||||
"imageTag": "af46386",
|
||||
"digest": "sha256:8cdf6922fda03703288a796b107f9185c3635f2768bc0bcf9b8272bf33ae7547",
|
||||
"publishState": "reused",
|
||||
"commitId": "278bbe1",
|
||||
"image": "127.0.0.1:5000/hwlab/hwlab-agent-skills:278bbe1",
|
||||
"imageTag": "278bbe1",
|
||||
"digest": "not_published",
|
||||
"publishState": "skeleton-only",
|
||||
"profile": "dev",
|
||||
"namespace": "hwlab-dev",
|
||||
"healthPath": "/health/live",
|
||||
@@ -498,8 +498,8 @@
|
||||
"publishEnabled": true,
|
||||
"artifactRequired": true,
|
||||
"artifactScope": "required",
|
||||
"notPublishedReason": null,
|
||||
"buildCreatedAt": "2026-05-27T00:14:48.439Z",
|
||||
"notPublishedReason": "publish_not_run",
|
||||
"buildCreatedAt": null,
|
||||
"buildSource": null,
|
||||
"componentCommitId": "a56ea7e33ef67040cc9a8748c3759b4f9c87ca46",
|
||||
"componentInputHash": "33cc71d1216f9f523499c950c445b6d6d4ac604e8048c1a2c3a9561b3bdce9e8",
|
||||
|
||||
@@ -6,8 +6,8 @@ G14 是 HWLAB 当前 DEV/PROD 原生 k8s 与 GitOps 运行面目标。G14 CI/CD
|
||||
|
||||
- Source of truth:业务版本以 Git source commit 为唯一身份;镜像 tag、OCI labels、runtime annotation 和 Argo CD desired state 都必须记录同一个 source commit。
|
||||
- CI:Tekton 在 G14 k3s 内运行 `hwlab-g14-ci-image-publish` Pipeline,由 `scripts/g14-gitops-render.mjs` 直接生成原生 task;最小校验固定为 `repo-reports-guard`、`g14-contract-check`、`codex-api-forwarder-check`,随后按 component plan 做 per-service BuildKit publish 与 GitOps promote;没有 `CI.json` runner、DIND 单任务发布或 Docker fallback。
|
||||
- Artifact:镜像使用 commit tag,例如 `127.0.0.1:5000/hwlab/hwlab-cloud-api:<shortCommit>`;digest 由 registry 返回,CI report 只作为审计证据,不作为 CD 真相。
|
||||
- Branch split:`G14` 是源码监控分支;`G14-gitops` 是 Tekton promotion 写入的生成分支,保存 `deploy/gitops/g14/**` desired state。
|
||||
- Artifact:镜像使用 commit tag,例如 `127.0.0.1:5000/hwlab/hwlab-cloud-api:<shortCommit>`;digest 由 registry 返回,CI report 只作为审计证据,不作为 CD 真相。发布态 artifact catalog 由 Tekton 写入 `G14-gitops:deploy/artifact-catalog.dev.json`。
|
||||
- Branch split:`G14` 是源码监控分支,只保存人写源码、声明和 seed contract;`G14-gitops` 是 Tekton promotion 写入的生成分支,保存 `deploy/artifact-catalog.dev.json` 与 `deploy/gitops/g14/**` desired state。CI/CD 不再把 catalog promotion commit 写回 `G14`。
|
||||
- CD:Argo CD 只消费 `G14-gitops:deploy/gitops/g14/runtime-dev` 与 `deploy/gitops/g14/runtime-prod` 的 Git desired state,不重新构建镜像,不读取 D601 状态,不获取 legacy DEV CD Lease。
|
||||
- FRP:G14 DEV 通过 `hwlab-dev/hwlab-g14-frpc` 暴露 `17666/17667`,G14 PROD 通过 `hwlab-prod/hwlab-g14-prod-frpc` 暴露 `18666/18667`;master frps 的 `deploy/frp/frps.dev.toml` 与实际 `/etc/frp/frps.toml` 必须同时放行这四个 G14 端口。
|
||||
- 并行性:不同 source commit 的 CI build 不共享发布锁;并行安全由 immutable commit tag/digest 和 Git desired state 保证。最终运行版本由 Argo CD 当前同步的 Git revision 决定。
|
||||
@@ -16,6 +16,8 @@ G14 是 HWLAB 当前 DEV/PROD 原生 k8s 与 GitOps 运行面目标。G14 CI/CD
|
||||
|
||||
`scripts/g14-gitops-render.mjs` 是 G14 专用转换器:
|
||||
|
||||
- 架构迁移时,过时的自检、预检、guard、gate 优先删除,不在旧门禁上叠加例外或复杂度;新门禁只允许覆盖明确高价值风险,必须保持最小、低噪声、易迁移。
|
||||
|
||||
- 直接声明 Tekton Pipeline、最小原语校验 task 和 PipelineRun 样板;不再读取 `CI.json` 或生成 `ci-json` step。
|
||||
- 读取 `deploy/deploy.json` 与 `deploy/k8s/*`,生成 Argo CD 可消费的 G14 runtime Kustomize path。
|
||||
- G14 Tekton 的镜像构建发布入口必须是 `scripts/g14-artifact-publish.mjs`;它只是集群内 Task 的 build/push helper,不做 rollout、不写 D601、不获取 legacy DEV CD Lease。旧 `scripts/dev-artifact-publish.mjs` 入口已删除;`dev-cd-apply`、`ci-publish` 和旧 `main` JS CD 入口禁止出现在 G14 Pipeline 生成脚本和 G14 验收证据中。
|
||||
@@ -23,7 +25,7 @@ G14 是 HWLAB 当前 DEV/PROD 原生 k8s 与 GitOps 运行面目标。G14 CI/CD
|
||||
- 生成 `hwlab-g14-branch-poller` CronJob:它使用 G14 集群内的 Git SSH Secret 轮询 `G14` 分支,按 source commit 创建确定命名的 Tekton PipelineRun。
|
||||
- 生成 `hwlab-g14-control-plane-reconciler` CronJob:它使用同一个 Git SSH Secret 轮询 `G14`,运行 repo 内 `scripts/g14-gitops-render.mjs`,并 server-side apply 生成的 Tekton RBAC、Pipeline、Poller 和 Reconciler manifests;因此 CI 控制面变化应自动进入 G14 k3s,不需要人工长期执行 render/apply。
|
||||
- 默认输出到 `deploy/gitops/g14/`。
|
||||
- 默认 GitOps 生成分支是 `G14-gitops`;Pipeline 成功后把本次 source commit 对应的 `deploy/gitops/g14/**` 推送到该分支,避免把生成提交继续写回 `G14`。
|
||||
- 默认 GitOps 生成分支是 `G14-gitops`;Pipeline 成功后把本次 source commit 对应的 `deploy/artifact-catalog.dev.json` 与 `deploy/gitops/g14/**` 推送到该分支,避免把生成提交继续写回 `G14`。
|
||||
- 默认 registry prefix 是 `127.0.0.1:5000/hwlab`,用于 G14 单节点 k3s 的 node-local registry。
|
||||
- 默认 CI/CD proxy 是 G14 本机 `http://127.0.0.1:10808` / `socks5h://127.0.0.1:10808`。Tekton CI step、BuildKit sidecar 和 publish step 都注入 proxy/no_proxy;服务镜像构建只允许通过 Pod 内 BuildKit Unix socket 直接 push 到 G14 本地 registry,不能回退到 Docker daemon、DIND 或 host Docker。
|
||||
- `prepare-source` 和最小原语校验 task 不允许每次运行时重新 `apk add`、`apt-get install` 或临时下载 browser/runtime 依赖。当前固定工具镜像是 `127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine-v1`,包含 Node 22、npm、Git、OpenSSH、curl 和 Python3。脚本启动时必须输出工具与 proxy preflight 结构化日志;缺少工具时应先重建并推送工具镜像,再修改 `HWLAB_G14_CI_TOOLS_IMAGE`/render 默认值,不能回退到 runtime 安装。
|
||||
@@ -36,9 +38,10 @@ G14 是 HWLAB 当前 DEV/PROD 原生 k8s 与 GitOps 运行面目标。G14 CI/CD
|
||||
|
||||
```sh
|
||||
npm run g14:gitops:render -- --source-revision <sourceCommit>
|
||||
npm run g14:gitops:check
|
||||
```
|
||||
|
||||
人工 source workspace 不再对 `deploy/gitops/g14/**` 做生成物对比;这些文件在 source 分支下是忽略的生成物,旧文件和 `source.json` 不是真相。需要看渲染计划时使用 `npm run g14:gitops:render -- --no-write`;正式发布态只看 `G14-gitops`、Argo Application 和 live runtime。
|
||||
|
||||
## 原生 k8s Tekton + Argo 配置面 vs 已废弃的 CI.json runner
|
||||
|
||||
当前 G14 已完全删除 `CI.json` 和 `ci-json` step。下面的对比只保留为迁移理由:Tekton + Argo 是唯一发布控制面;repo-local 校验已经收敛为 render 内建的少量原语 task,而不是再保留一条 shell runner 路线。
|
||||
@@ -62,13 +65,13 @@ npm run g14:gitops:check
|
||||
|
||||
HWLAB 是 monorepo,G14 CI/CD 加速必须按组件输入判断构建和滚动,并直接依赖内建 component model、`deploy/deploy.json` 与 per-service artifact catalog;`CI.json` 已删除,不再作为任何 planner 输入。
|
||||
|
||||
- `scripts/g14-ci-plan.mjs` 是只读 planner,默认读取 `deploy/deploy.json`、`deploy/artifact-catalog.dev.json` 和 `scripts/src/g14-ci-plan-lib.mjs` 内建 component model,输出 `affectedServices`、`reusedServices`、`componentCommitId`、`componentInputHash`、`dockerfileHash`、`baseImageDigest`、`buildArgsHash` 和原因;它不得修改 deploy、catalog 或 GitOps 文件。
|
||||
- `scripts/g14-ci-plan.mjs` 是只读 planner,默认读取当前 workspace 的 `deploy/deploy.json`、`deploy/artifact-catalog.dev.json` 和 `scripts/src/g14-ci-plan-lib.mjs` 内建 component model,输出 `affectedServices`、`reusedServices`、`componentCommitId`、`componentInputHash`、`dockerfileHash`、`baseImageDigest`、`buildArgsHash` 和原因;它不得修改 deploy、catalog 或 GitOps 文件。Tekton `prepare-source` 会先从 `G14-gitops` 注入上一轮发布态 catalog,source 分支里的 catalog 只作为 seed contract。
|
||||
- 服务清单兼容顺序固定为:显式 `--services`、`deploy.services[]`、`deploy.k3s.serviceMappings[]`、`internal/protocol.SERVICE_IDS`。因此旧 `deploy/deploy.json` 形态和当前完整 `deploy.services[]` 形态都必须能被 planner 识别。
|
||||
- 组件边界固定由 `scripts/src/g14-ci-plan-lib.mjs` 的内建 service-path model 定义;如需新增或调整 `componentPaths`、`sharedPaths`、`runtimeDeps` 或 `buildSystemPaths`,直接修改 planner 库和对应测试,不再额外维护 repo-local commands/forbidden skeleton。
|
||||
- `scripts/g14-artifact-publish.mjs` 默认启用组件级 lazy build:先运行 planner,再只构建/推送 `affectedServices`,`reusedServices` 必须从 `deploy/artifact-catalog.dev.json` 复用已有 sha256 digest;如果 catalog 没有可验证 digest,必须结构化阻塞并修复 catalog 或显式扩大 changed service 范围,不能退回 Docker 或 legacy full-build 路线。
|
||||
- `scripts/g14-artifact-publish.mjs` 的 publish report 必须携带 planner 的 per-service 元数据;`scripts/refresh-artifact-catalog.mjs` 只把生成的 `commitId`、`image`、`imageTag`、`digest`、`publishState` 和 component provenance 字段复制进 `deploy/artifact-catalog.dev.json`。`deploy/deploy.json` 是人写的 runtime config 真相源,不得被 promotion 回写镜像身份字段。
|
||||
- `scripts/g14-artifact-publish.mjs` 默认启用组件级 lazy build:先运行 planner,再只构建/推送 `affectedServices`,`reusedServices` 从 `deploy/artifact-catalog.dev.json` 复用已有 sha256 digest;如果 catalog 没有可验证 digest,planner 直接把该服务列为 affected 并重新发布,不能用旧 guard 阻塞,也不能退回 Docker 或 legacy full-build 路线。
|
||||
- `scripts/g14-artifact-publish.mjs` 的 publish report 必须携带 planner 的 per-service 元数据;`scripts/refresh-artifact-catalog.mjs` 只把生成的 `commitId`、`image`、`imageTag`、`digest`、`publishState` 和 component provenance 字段复制进当前 workspace 的 `deploy/artifact-catalog.dev.json`。Tekton promotion 只把刷新后的 catalog 写入 `G14-gitops`;`deploy/deploy.json` 是人写的 runtime config 真相源,不得被 promotion 回写镜像身份字段。
|
||||
- `scripts/g14-gitops-render.mjs` 只支持混合 desired state:workload 的 container image、`HWLAB_IMAGE`、`HWLAB_IMAGE_TAG` 和 pod template `source-commit` 来自 `deploy/artifact-catalog.dev.json` 的 per-service artifact identity;普通 env、replica、healthPath、profile 等配置来自 `deploy/deploy.json`。全局 GitOps metadata 仍记录本次 source commit。`--legacy-source-images` 与 `HWLAB_G14_USE_DEPLOY_IMAGES=0` 已废弃,不得把所有 workload image 回退渲染为同一个 source commit tag。
|
||||
- G14 Tekton promotion 在推送 `G14-gitops` 前,必须用 publish report 刷新 workspace 内的 `deploy/artifact-catalog.dev.json`,并且只在 catalog 变化时以 `chore: promote G14 GitOps source <short>` 提交回 `G14` source branch;poller 必须跳过这类生成提交,避免自触发循环。promotion 不得自动修改 `deploy/deploy.json` 或 `deploy/k8s/base/workloads.yaml`,这样人写配置不会和 CI 生成身份反复冲突。
|
||||
- G14 Tekton promotion 在推送 `G14-gitops` 前,必须用 publish report 刷新 workspace 内的 `deploy/artifact-catalog.dev.json`,然后把刷新后的 catalog 和 rendered GitOps desired state 一起提交到 `G14-gitops`。promotion 不得自动修改或推送 `G14` source branch,也不得自动修改 `deploy/deploy.json` 或 `deploy/k8s/base/workloads.yaml`,这样人写配置不会和 CI 生成身份反复冲突。
|
||||
- Tekton 并发化只能以 planner 输出作为输入;每个 service 都有独立 TaskRun,changed service 启动 BuildKit,unchanged service 只写 reuse result 并复用 catalog digest,且不能改 pod template,避免无意义 rollout。没有完整 per-service desired state 证据时,必须修复 planner/catalog 证据,不能使用 `--full-build`、`--legacy-source-images`、DIND 或 Docker fallback 回退。
|
||||
|
||||
## 加速判定与当前瓶颈
|
||||
@@ -85,7 +88,7 @@ G14 CI/CD 加速的第一判定标准不是 PipelineRun 总耗时单点变短,
|
||||
当前仍然慢的主要位置:
|
||||
|
||||
- `prepare-source` 与最小原语校验 task 仍是前段主要开销。它们包含 Git clone、checkout、必要的 `npm ci`、`repo-reports-guard`、`g14-contract-check` 和 `codex-api-forwarder-check`。继续加速应优先减少 workspace cold start 与依赖安装成本,而不是重新塞回一条 shell runner。
|
||||
- `gitops-promote` 仍是大头,当前量级约 100 秒。它包含 source branch freshness check、artifact catalog refresh、render/check、clone `G14-gitops`、复制 generated desired state、commit 和 push。继续加速应优先减少 git clone/push 成本,例如使用浅 clone、持久 workspace、server-side patch 或把 GitOps repo 操作单独缓存。
|
||||
- `gitops-promote` 仍是大头,当前量级约 100 秒。它包含 source branch freshness check、artifact catalog refresh、render、clone `G14-gitops`、复制 catalog 与 generated desired state、commit 和 push。继续加速应优先减少 git clone/push 成本,例如使用浅 clone、持久 workspace、server-side patch 或把 GitOps repo 操作单独缓存。
|
||||
- per-service TaskRun 数量增加后会有 k8s 调度和 sidecar 生命周期开销。reuse-only 任务很快,但如果每个 unchanged service 都启动完整 buildkit sidecar,调度开销会抵消部分收益;长期目标是让 unchanged service 走更轻量的 reuse Task,changed service 才启动 BuildKit。
|
||||
- per-service BuildKit 必须使用 Pod 内 Unix socket 或其他 Pod-scoped 端点,禁止在 hostNetwork 下绑定固定 TCP 端口。多个 service TaskRun 并发时,如果 sidecar 统一监听 `0.0.0.0:1234`,会抢同一个宿主端口,导致主 step 结果成功但 Pod phase 显示 Failed,严重污染 CI 观测。
|
||||
- Argo sync/health 不是 instant。`Sync=Synced` 后 health 仍可能因为 replicas=0 的模板 Deployment、未排除 health 的模板 Job 或 StatefulSet 旧 revision 显示 `Suspended`/`Progressing`。这时应先看实际长驻 workload ready 与具体 unhealthy resource,不要只看 Application 总 health;如果 suspended Job 只是模板资源,应修 render 注解而不是把 DEV rollout 判成失败。
|
||||
@@ -117,9 +120,9 @@ DeepSeek proxy manifest 是 GitOps desired state 的一部分,DEV/PROD 分别
|
||||
|
||||
G14 不要求 GitHub webhook 或 GitHub Actions 配置。`hwlab-ci/hwlab-g14-branch-poller` CronJob 每 1 分钟通过 Git SSH 拉取 `G14` HEAD,并用 source commit 的前 12 位生成 PipelineRun 名称 `hwlab-g14-ci-poll-<short12>`。
|
||||
|
||||
如果同名 PipelineRun 已存在,poller 直接跳过;如果不存在,则创建新的 PipelineRun。这样可以用 Kubernetes 原生 CronJob、ServiceAccount、RBAC 和 Tekton API 实现无 GitHub webhook 的分支监控,同时避免同一个 commit 被反复派单。历史上的 `chore: promote G14 GitOps source ...` 生成提交会被 poller 跳过。
|
||||
如果同名 PipelineRun 已存在,poller 直接跳过;如果不存在,则创建新的 PipelineRun。这样可以用 Kubernetes 原生 CronJob、ServiceAccount、RBAC 和 Tekton API 实现无 GitHub webhook 的分支监控,同时避免同一个 commit 被反复派单。历史上的 `chore: promote G14 GitOps source ...` source 分支生成提交已经废弃;poller 只应该看到人写 source commit。
|
||||
|
||||
Pipeline 的标准路径是:`G14` source commit -> Tekton 原语校验 task -> commit-tagged image push 到 G14 registry -> render `deploy/gitops/g14/**` -> push `G14-gitops` -> Argo CD 同步 runtime。CD 只消费已经构建好的镜像和 Git desired state,不在 Argo CD 内构建镜像。
|
||||
Pipeline 的标准路径是:`G14` source commit -> Tekton 原语校验 task -> commit-tagged image push 到 G14 registry -> refresh artifact catalog -> render `deploy/gitops/g14/**` -> push catalog 与 GitOps desired state 到 `G14-gitops` -> Argo CD 同步 runtime。CD 只消费已经构建好的镜像和 Git desired state,不在 Argo CD 内构建镜像。
|
||||
|
||||
GitOps promotion 成功只证明 `G14-gitops` 分支已经写入新 desired state;如果 render 改变了 Argo Application、AppProject、runtime path 或 DEV/PROD 拆分目录,必须同步检查并应用 `deploy/gitops/g14/argocd/project.yaml`、`application-dev.yaml` 和 `application-prod.yaml`。`hwlab-g14-dev` 必须指向 `deploy/gitops/g14/runtime-dev`,`hwlab-g14-prod` 必须指向 `deploy/gitops/g14/runtime-prod`;如果集群里 Application 仍指向旧 `deploy/gitops/g14/runtime`,Argo 会停在旧 revision,即使 CI/publish/promote 全部成功也不会滚动新镜像。
|
||||
|
||||
@@ -169,14 +172,14 @@ G14 PR、CI、CD 的判断应按以下顺序收敛真相,越靠前越接近最
|
||||
1. live runtime:目标 namespace 的 Deployment/StatefulSet template、Pod ready、`describe`、容器日志和公网 health。
|
||||
2. Argo desired state:`hwlab-g14-dev` / `hwlab-g14-prod` 的 Application revision、sync、health 和实际 runtime path。
|
||||
3. Tekton 执行证据:branch-poller 日志、PipelineRun、TaskRun results、`gitops-promote` 终态。
|
||||
4. 干净 source workspace:`origin/G14` 当前内容,以及 `npm run g14:gitops:render`、`npm run g14:gitops:check` 和 planner 输出。
|
||||
4. 干净 source workspace:`origin/G14` 当前内容,以及 `npm run g14:gitops:render -- --no-write` 和 planner 输出。
|
||||
5. 对照线索:旧 commit 记忆、坏 worktree、D601 legacy 路径、脚本旧默认值,只能当线索,不能当真相。
|
||||
|
||||
常见误判与纠正:
|
||||
|
||||
- 不要把 `/root/hwlab` 当前 checkout、任意 `/tmp` worktree 或带 conflict marker 的目录当 source truth;只有跟踪 `origin/G14` 的干净 worktree 才能作为当前发布依据。
|
||||
- 不要按“某个预期 commit 是否出现在 ancestry 中”判断功能是否已经合入;当前 `origin/G14` 实际内容比历史 commit 轨迹更重要。
|
||||
- 改了 source 但没先 render/check 时,generated mismatch 只说明 source 与 generated 没对齐,不等于 Tekton 或 Argo 本身坏掉。
|
||||
- 改了 source 但没有触发 Tekton promotion 时,`G14-gitops` 仍停在旧 generated state 是正常现象,不等于 Tekton 或 Argo 本身坏掉。
|
||||
- 不要把 `build-*` TaskRun 名称直接当作镜像重建证据;reuse-only 变更也会扇出同名 TaskRun,必须看 Tekton result 的 `status=reused` 与 `build-backend=reused-catalog`。
|
||||
- 不要把 `G14-gitops` 分支已更新误判成 DEV 或 PROD 已滚动;只有 Argo Application revision、目标 workload ready 和 live manifest 生效,才算 CD 真实通过。
|
||||
- 不要把 health payload 中的镜像 commit 当成 runtime manifest commit;runtime-only 修复可能复用旧镜像,只改变 probe、env、annotation 或 sidecar 行为。
|
||||
@@ -200,14 +203,13 @@ bun scripts/cli.ts ssh G14:/root/hwlab shell 'git status --short --branch && git
|
||||
2. Source / PR 预检
|
||||
|
||||
```sh
|
||||
npm run g14:gitops:render
|
||||
npm run g14:gitops:check
|
||||
npm run g14:gitops:render -- --no-write
|
||||
node scripts/g14-ci-plan.mjs --base-ref origin/G14 --target-ref HEAD --pretty
|
||||
```
|
||||
|
||||
- 先看当前内容和长期参考,不按旧 commit 记忆判断“功能是否已合入”。
|
||||
- GitOps、manifest、poller/reconciler、provider profile 相关改动必须先跑 render/check;planner 用于判断本轮是 docs-only、reuse-only 还是需要真实构建。
|
||||
- provider/profile 变更在进入 PR 或 CI 之前,先按 `code-agent-chat-readiness.md` 做目标 Pod 最小闭环;不要把完整 CI/CD 当成 transport 试错工具。
|
||||
- GitOps、manifest、poller/reconciler、provider profile 相关改动先跑 render no-write;planner 用于判断本轮是 docs-only、reuse-only 还是需要真实构建。不要在 source 分支对 ignored generated output 跑 drift check。
|
||||
- provider/profile 变更在进入 PR 或 CI 之前,先按 `code-agent-chat-readiness.md` 做目标 Pod 最小���环;不要把完整 CI/CD 当成 transport 试错工具。
|
||||
|
||||
3. 合并或推送到 `G14`
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
"web:build": "node web/hwlab-cloud-web/scripts/build.mjs",
|
||||
"artifact-catalog:refresh-blocked": "node scripts/refresh-artifact-catalog.mjs --target-ref origin/main --blocked",
|
||||
"g14:gitops:render": "node scripts/g14-gitops-render.mjs",
|
||||
"g14:gitops:check": "node scripts/g14-gitops-render.mjs --check",
|
||||
"cicd:jobs": "node tools/hwlab-cli/bin/hwlab-cli.mjs cicd jobs",
|
||||
"docs:validate:m3-rollout": "node scripts/validate-m3-rollout-runbook.mjs",
|
||||
"d601:k3s:readonly": "node scripts/d601-k3s-readonly-observability.mjs",
|
||||
|
||||
@@ -42,6 +42,19 @@ test("G14 CI planner treats docs-only changes as no image build", async () => {
|
||||
assert.equal(plan.changedPathSummary.docsOnly, true);
|
||||
});
|
||||
|
||||
test("planner rebuilds when catalog digest is missing instead of blocking reuse", async () => {
|
||||
const repo = await createFixtureRepo({ catalog: "missing-digest" });
|
||||
await mkdir(path.join(repo, "docs/reference"), { recursive: true });
|
||||
await writeFile(path.join(repo, "docs/reference/g14-gitops-cicd.md"), "docs v2\n");
|
||||
await git(repo, ["add", "."]);
|
||||
await git(repo, ["commit", "-m", "change docs"]);
|
||||
|
||||
const plan = await createG14CiPlan({ repoRoot: repo, baseRef: "HEAD~1", targetRef: "HEAD" });
|
||||
assert.deepEqual(plan.affectedServices, ["hwlab-cloud-api", "hwlab-cloud-web"]);
|
||||
assert.equal(plan.imageBuildRequired, true);
|
||||
assert.equal(plan.services[0].reason.includes("catalog-digest-missing"), true);
|
||||
});
|
||||
|
||||
test("component model uses built-in service paths", () => {
|
||||
const models = componentModelsForServices(["hwlab-cloud-api"]);
|
||||
assert.deepEqual(models[0].componentPaths, [
|
||||
@@ -84,6 +97,10 @@ async function createFixtureRepo(options = {}) {
|
||||
await mkdir(path.join(repo, "web/hwlab-cloud-web"), { recursive: true });
|
||||
await mkdir(path.join(repo, "deploy"), { recursive: true });
|
||||
await writeFile(path.join(repo, "deploy/deploy.json"), JSON.stringify(createDeployFixture({ deployServices, k3sServiceMappings }), null, 2));
|
||||
const catalogMode = options.catalog ?? "ready";
|
||||
if (catalogMode !== false) {
|
||||
await writeFile(path.join(repo, "deploy/artifact-catalog.dev.json"), JSON.stringify(createCatalogFixture(catalogMode), null, 2));
|
||||
}
|
||||
await writeFile(path.join(repo, "package.json"), JSON.stringify({ type: "module" }, null, 2));
|
||||
await writeFile(path.join(repo, "package-lock.json"), JSON.stringify({ lockfileVersion: 3 }, null, 2));
|
||||
await writeFile(path.join(repo, "cmd/hwlab-cloud-api/main.mjs"), "console.log('api');\n");
|
||||
@@ -115,6 +132,19 @@ function createDeployFixture({ deployServices, k3sServiceMappings }) {
|
||||
return deploy;
|
||||
}
|
||||
|
||||
function createCatalogFixture(mode) {
|
||||
const digest = mode === "ready" ? `sha256:${"1".repeat(64)}` : "not_published";
|
||||
return {
|
||||
services: ["hwlab-cloud-api", "hwlab-cloud-web"].map((serviceId) => ({
|
||||
serviceId,
|
||||
commitId: "abc1234",
|
||||
image: `127.0.0.1:5000/hwlab/${serviceId}:abc1234`,
|
||||
imageTag: "abc1234",
|
||||
digest
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
async function git(cwd, args) {
|
||||
return execFileAsync("git", ["-c", "user.name=HWLAB Test", "-c", "user.email=hwlab-test@example.invalid", ...args], { cwd });
|
||||
}
|
||||
|
||||
@@ -239,20 +239,6 @@ async function resolveSourceRevision(value) {
|
||||
return { full, short: full.slice(0, 7) };
|
||||
}
|
||||
|
||||
async function defaultCheckSourceRevision(args) {
|
||||
if (!args.check || args.sourceRevision) return args;
|
||||
try {
|
||||
const raw = await readFile(generatedPath(path.join(args.outDir, "source.json")), "utf8");
|
||||
const parsed = JSON.parse(raw);
|
||||
if (typeof parsed.sourceCommit === "string" && sourceCommitPattern.test(parsed.sourceCommit)) {
|
||||
return { ...args, sourceRevision: parsed.sourceCommit };
|
||||
}
|
||||
} catch (error) {
|
||||
if (error?.code !== "ENOENT") throw error;
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
function jsonManifest(value) {
|
||||
return `${JSON.stringify(value, null, 2)}\n`;
|
||||
}
|
||||
@@ -729,6 +715,17 @@ function prepareSourceScript() {
|
||||
"git config --global --add safe.directory /workspace/source/repo",
|
||||
"git checkout \"$(params.revision)\"",
|
||||
"test \"$(git rev-parse HEAD)\" = \"$(params.revision)\"",
|
||||
"if git ls-remote --exit-code --heads origin \"$(params.gitops-branch)\" >/dev/null 2>&1; then",
|
||||
" git fetch --depth 1 origin \"$(params.gitops-branch)\" >/dev/null 2>&1 || true",
|
||||
" if git cat-file -e FETCH_HEAD:deploy/artifact-catalog.dev.json 2>/dev/null; then",
|
||||
" git show FETCH_HEAD:deploy/artifact-catalog.dev.json > deploy/artifact-catalog.dev.json",
|
||||
" echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"loaded\",\"branch\":\"'\"$(params.gitops-branch)\"'\"}'",
|
||||
" else",
|
||||
" echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seed\",\"reason\":\"missing-on-gitops-branch\"}'",
|
||||
" fi",
|
||||
"else",
|
||||
" echo '{\"event\":\"gitops-artifact-catalog\",\"phase\":\"prepare-source\",\"status\":\"seed\",\"reason\":\"gitops-branch-missing\"}'",
|
||||
"fi",
|
||||
"npm ci --ignore-scripts",
|
||||
`echo ${shellSingleQuote(`G14 prepare-source complete; validation task count=${primitiveValidationTasks.length}`)}`
|
||||
];
|
||||
@@ -918,17 +915,10 @@ git config --global user.name "HWLAB G14 GitOps Bot"
|
||||
git config --global user.email "hwlab-g14-gitops-bot@users.noreply.github.com"
|
||||
if [ -s /workspace/source/dev-artifacts.json ]; then
|
||||
node scripts/refresh-artifact-catalog.mjs --target-ref "$(params.revision)" --publish-report /workspace/source/dev-artifacts.json
|
||||
if ! git diff --quiet -- deploy/artifact-catalog.dev.json; then
|
||||
short="$(printf '%.7s' "$(params.revision)")"
|
||||
git add deploy/artifact-catalog.dev.json
|
||||
git commit -m "chore: promote G14 GitOps source $short"
|
||||
git push origin "HEAD:$(params.source-branch)"
|
||||
fi
|
||||
fi
|
||||
source_state_revision="$(git rev-parse HEAD)"
|
||||
node scripts/g14-gitops-render.mjs --source-revision "$(params.revision)" --source-repo "$(params.git-url)" --source-branch "$(params.source-branch)" --gitops-branch "$(params.gitops-branch)" --registry-prefix "$(params.registry-prefix)" --use-deploy-images
|
||||
node scripts/g14-gitops-render.mjs --source-revision "$(params.revision)" --source-repo "$(params.git-url)" --source-branch "$(params.source-branch)" --gitops-branch "$(params.gitops-branch)" --registry-prefix "$(params.registry-prefix)" --use-deploy-images --check
|
||||
check_source_head before-push "$source_state_revision"
|
||||
check_source_head before-push
|
||||
workdir="$(mktemp -d)"
|
||||
git clone --no-checkout "$(params.git-url)" "$workdir/gitops"
|
||||
cd "$workdir/gitops"
|
||||
@@ -942,7 +932,8 @@ else
|
||||
fi
|
||||
mkdir -p deploy/gitops
|
||||
cp -a /workspace/source/repo/deploy/gitops/g14 deploy/gitops/g14
|
||||
git add deploy/gitops/g14
|
||||
cp /workspace/source/repo/deploy/artifact-catalog.dev.json deploy/artifact-catalog.dev.json
|
||||
git add deploy/artifact-catalog.dev.json deploy/gitops/g14
|
||||
if git diff --cached --quiet; then
|
||||
echo '{"status":"unchanged","gitopsBranch":"'"$(params.gitops-branch)"'","sourceRevision":"'"$(params.revision)"'"}'
|
||||
exit 0
|
||||
@@ -1492,6 +1483,7 @@ function tektonPipeline() {
|
||||
params: [
|
||||
{ name: "git-url" },
|
||||
{ name: "source-branch" },
|
||||
{ name: "gitops-branch" },
|
||||
{ name: "revision" }
|
||||
],
|
||||
workspaces: [{ name: "source" }, { name: "git-ssh" }],
|
||||
@@ -1500,6 +1492,7 @@ function tektonPipeline() {
|
||||
params: [
|
||||
{ name: "git-url", value: "$(params.git-url)" },
|
||||
{ name: "source-branch", value: "$(params.source-branch)" },
|
||||
{ name: "gitops-branch", value: "$(params.gitops-branch)" },
|
||||
{ name: "revision", value: "$(params.revision)" }
|
||||
]
|
||||
},
|
||||
@@ -2373,7 +2366,6 @@ async function main() {
|
||||
console.log(usage());
|
||||
return;
|
||||
}
|
||||
args = await defaultCheckSourceRevision(args);
|
||||
ensureObject(args, "args");
|
||||
const { files, source } = await plannedFiles(args);
|
||||
if (args.check) {
|
||||
|
||||
@@ -1186,7 +1186,7 @@ function nextCommands({ canPublish, canApply, blockedReasons }) {
|
||||
return [
|
||||
"node scripts/deploy-desired-state-plan.mjs --target-ref origin/main --pretty",
|
||||
"node scripts/deploy-desired-state-plan.mjs --promotion-commit <origin-main-sha> --check",
|
||||
"node scripts/g14-gitops-render.mjs --check",
|
||||
"node scripts/g14-gitops-render.mjs --no-write",
|
||||
guardCommand
|
||||
];
|
||||
}
|
||||
@@ -1199,7 +1199,7 @@ function nextCommands({ canPublish, canApply, blockedReasons }) {
|
||||
];
|
||||
}
|
||||
return [
|
||||
"node scripts/g14-gitops-render.mjs --check",
|
||||
"node scripts/g14-gitops-render.mjs --no-write",
|
||||
"node scripts/artifact-runtime-readiness-guard.mjs --target-ref origin/main --check --live --no-report"
|
||||
];
|
||||
}
|
||||
|
||||
@@ -101,7 +101,9 @@ export async function createG14CiPlan(options = {}) {
|
||||
...runtimeDepMatches,
|
||||
...buildSystemMatches
|
||||
].filter((item) => !isTestOnlyPath(item) && !isDocsOnlyPath(item)));
|
||||
const affected = imageRelevantChangedPaths.length > 0;
|
||||
const catalogRecord = catalogByServiceId.get(model.serviceId) ?? null;
|
||||
const catalogReuse = reuseCandidate(catalogRecord, imageRelevantChangedPaths.length > 0);
|
||||
const affected = imageRelevantChangedPaths.length > 0 || catalogReuse.status === "candidate-no-catalog" || catalogReuse.status === "candidate-unverified-digest";
|
||||
const componentInputPaths = uniqueSorted([
|
||||
...model.componentPaths,
|
||||
...model.sharedPaths,
|
||||
@@ -119,7 +121,6 @@ export async function createG14CiPlan(options = {}) {
|
||||
runtimeKind: model.runtimeKind,
|
||||
entrypoint: model.entrypoint
|
||||
});
|
||||
const catalogRecord = catalogByServiceId.get(model.serviceId) ?? null;
|
||||
services.push({
|
||||
serviceId: model.serviceId,
|
||||
runtimeKind: model.runtimeKind,
|
||||
@@ -136,7 +137,9 @@ export async function createG14CiPlan(options = {}) {
|
||||
buildArgsHash,
|
||||
changedPaths: imageRelevantChangedPaths,
|
||||
affected,
|
||||
reason: affected ? reasonForService({ directMatches, sharedMatches, runtimeDepMatches, buildSystemMatches }) : reasonForUnchanged(globalChange),
|
||||
reason: affected
|
||||
? reasonForService({ directMatches, sharedMatches, runtimeDepMatches, buildSystemMatches, catalogReuse })
|
||||
: reasonForUnchanged(globalChange),
|
||||
reuse: reuseCandidate(catalogRecord, affected)
|
||||
});
|
||||
}
|
||||
@@ -315,12 +318,14 @@ async function gitValue(repoRoot, args) {
|
||||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
function reasonForService({ directMatches, sharedMatches, runtimeDepMatches, buildSystemMatches }) {
|
||||
function reasonForService({ directMatches, sharedMatches, runtimeDepMatches, buildSystemMatches, catalogReuse = null }) {
|
||||
const reasons = [];
|
||||
if (directMatches.some((item) => !isTestOnlyPath(item) && !isDocsOnlyPath(item))) reasons.push("component-path-changed");
|
||||
if (sharedMatches.some((item) => !isTestOnlyPath(item) && !isDocsOnlyPath(item))) reasons.push("shared-runtime-changed");
|
||||
if (runtimeDepMatches.some((item) => !isTestOnlyPath(item) && !isDocsOnlyPath(item))) reasons.push("runtime-deps-changed");
|
||||
if (buildSystemMatches.some((item) => !isTestOnlyPath(item) && !isDocsOnlyPath(item))) reasons.push("build-system-changed");
|
||||
if (catalogReuse?.status === "candidate-no-catalog") reasons.push("catalog-record-missing");
|
||||
if (catalogReuse?.status === "candidate-unverified-digest") reasons.push("catalog-digest-missing");
|
||||
return reasons.length ? reasons : ["affected"];
|
||||
}
|
||||
|
||||
|
||||
@@ -196,7 +196,6 @@ function assertCatalogServices(catalog, deployManifest, catalogMode) {
|
||||
assert.ok(deployService, `${context} missing from deploy manifest`);
|
||||
|
||||
assertCommitId(service.commitId, `${context} commitId`);
|
||||
assert.equal(service.commitId, catalog.commitId, `${context} commitId must match catalog`);
|
||||
assert.equal(service.imageTag, service.commitId.slice(0, 7), `${context} imageTag must be the short commit`);
|
||||
assertNoMutableTag(service.imageTag, `${context} imageTag`);
|
||||
|
||||
@@ -233,7 +232,7 @@ function assertCatalogServices(catalog, deployManifest, catalogMode) {
|
||||
assert.equal(service.digest, "not_published", `${context} digest`);
|
||||
} else {
|
||||
if (service.artifactRequired) {
|
||||
assert.equal(service.publishState, "published", `${context} publishState`);
|
||||
assert.ok(["published", "reused"].includes(service.publishState), `${context} publishState`);
|
||||
assert.match(service.digest, digestPattern, `${context} digest`);
|
||||
} else {
|
||||
assert.equal(service.publishState, "skeleton-only", `${context} publishState`);
|
||||
|
||||
Reference in New Issue
Block a user