diff --git a/docs/reference/spec-v02-cicd.md b/docs/reference/spec-v02-cicd.md index 2c15f789..9247adcd 100644 --- a/docs/reference/spec-v02-cicd.md +++ b/docs/reference/spec-v02-cicd.md @@ -219,6 +219,8 @@ P1 no-op runtime skip 只跳过无实际 runtime 变化的等待。planner 输 `runtime-ready` 只能观察本轮 `rolloutServices` 对应的 workload readiness,不能把整个 `hwlab-v02` namespace 的全量 workload 都绑定到当前 source commit 后等待。复用 service 的 Deployment metadata 可以记录本次 source commit 作为 GitOps revision 线索,但 Pod template identity 必须来自该 service 的 artifact/runtime commit、boot commit 或配置内容 hash;否则 catalog 复用会被误转成不必要 rollout,`runtime-ready` 会被无关服务拖慢。`hwlab-cloud-api` 的 rollout 允许连带观察 `hwlab-deepseek-proxy`,因为 deepseek bridge 复用 cloud-api 镜像作为运行载体;其他连带关系必须有明确运行依赖后再加入观察集合。基础对象如 FRP、Postgres 的 Pod template 不得写入全局 source commit,应用配置 hash 或 migration hash 表达真实重启边界。 +Kubernetes label 里的配置 hash 必须使用短 hash,完整 64 位 sha256 只能放 annotation。FRP `config-sha256`、Postgres `migration-sha256` 这类字段如果进入 Pod template label,长度必须小于 Kubernetes label value 的 63 字节上限;annotation 保留完整 hash 用于排障和人工比对。Argo sync 报 `must be no more than 63 bytes` 时,优先检查是否把完整 sha256 写入 label,不要通过删除 hash、放宽 sync 或忽略 OutOfSync 绕过。 + 快速优化不能绕过 fail-closed 语义。mirror miss、commit ancestry 不合法、boot script 缺失、digest 缺失、Argo observer RBAC 不足、workload 未 ready 或公网 health 不一致,都不能为了追求耗时而降级为 warning。允许跳过的只有已经证明 runtime desired state 没有实际变化的等待。 ## 快速排障 diff --git a/scripts/g14-gitops-render.mjs b/scripts/g14-gitops-render.mjs index 994af532..88d4b786 100644 --- a/scripts/g14-gitops-render.mjs +++ b/scripts/g14-gitops-render.mjs @@ -76,6 +76,11 @@ const codexApiForwarderPort = process.env.HWLAB_G14_CODEX_API_FORWARDER_PORT || const codexApiForwarderUpstreamBaseUrl = process.env.HWLAB_G14_CODEX_API_UPSTREAM_BASE_URL || "https://hyueapi.com"; const sourceCommitPattern = /^[a-f0-9]{7,40}$/u; const v02EnvReuseRuntimeMode = "env-reuse-git-mirror-checkout"; + +function k8sLabelHash(value) { + return String(value || "").slice(0, 16); +} + const primitiveValidationTasks = Object.freeze([ { name: "repo-reports-guard", @@ -3580,7 +3585,7 @@ localPort = 6667 remotePort = ${edgeRemotePort} `; const configSha256 = createHash("sha256").update(configText).digest("hex"); - const templateLabels = { ...labels, "hwlab.pikastech.local/config-sha256": configSha256 }; + const templateLabels = { ...labels, "hwlab.pikastech.local/config-sha256": k8sLabelHash(configSha256) }; const templateAnnotations = { "hwlab.pikastech.local/config-sha256": configSha256 }; return { apiVersion: "v1", @@ -4036,7 +4041,7 @@ function v02PostgresManifest({ migrationSql, source }) { "hwlab.pikastech.local/environment": "v02", "hwlab.pikastech.local/gitops-target": "v02", "hwlab.pikastech.local/profile": "v02", - "hwlab.pikastech.local/migration-sha256": migrationSha256 + "hwlab.pikastech.local/migration-sha256": k8sLabelHash(migrationSha256) }; const templateAnnotations = { "hwlab.pikastech.local/migration-sha256": migrationSha256 }; return { diff --git a/scripts/g14-gitops-render.test.ts b/scripts/g14-gitops-render.test.ts index c40cd23d..02c49a1a 100644 --- a/scripts/g14-gitops-render.test.ts +++ b/scripts/g14-gitops-render.test.ts @@ -243,12 +243,22 @@ test("v02 render follows TypeScript runtime checks and does not self-patch boots } const frpc = JSON.parse(await readFile(path.join(outDir, "runtime-v02", "g14-frpc.yaml"), "utf8")); const frpcDeployment = frpc.items.find((item) => item.kind === "Deployment"); - assert.ok(frpcDeployment.spec.template.metadata.labels["hwlab.pikastech.local/config-sha256"]); + const frpcConfigLabel = frpcDeployment.spec.template.metadata.labels["hwlab.pikastech.local/config-sha256"]; + const frpcConfigAnnotation = frpcDeployment.spec.template.metadata.annotations["hwlab.pikastech.local/config-sha256"]; + assert.ok(frpcConfigLabel); + assert.equal(frpcConfigLabel.length <= 63, true); + assert.match(frpcConfigAnnotation, /^[a-f0-9]{64}$/u); + assert.equal(frpcConfigAnnotation.startsWith(frpcConfigLabel), true); assert.equal(frpcDeployment.spec.template.metadata.labels["hwlab.pikastech.local/source-commit"], undefined); const postgres = JSON.parse(await readFile(path.join(outDir, "runtime-v02", "postgres.yaml"), "utf8")); const postgresStatefulSet = postgres.items.find((item) => item.kind === "StatefulSet"); - assert.ok(postgresStatefulSet.spec.template.metadata.labels["hwlab.pikastech.local/migration-sha256"]); + const postgresMigrationLabel = postgresStatefulSet.spec.template.metadata.labels["hwlab.pikastech.local/migration-sha256"]; + const postgresMigrationAnnotation = postgresStatefulSet.spec.template.metadata.annotations["hwlab.pikastech.local/migration-sha256"]; + assert.ok(postgresMigrationLabel); + assert.equal(postgresMigrationLabel.length <= 63, true); + assert.match(postgresMigrationAnnotation, /^[a-f0-9]{64}$/u); + assert.equal(postgresMigrationAnnotation.startsWith(postgresMigrationLabel), true); assert.equal(postgresStatefulSet.spec.template.metadata.labels["hwlab.pikastech.local/source-commit"], undefined); await assert.rejects(() => readFile(path.join(outDir, "tekton-v02", "control-plane-reconciler.yaml"), "utf8"), { code: "ENOENT" });