diff --git a/docs/reference/agentrun.md b/docs/reference/agentrun.md index fcd277b9..f43cb0dc 100644 --- a/docs/reference/agentrun.md +++ b/docs/reference/agentrun.md @@ -111,7 +111,7 @@ HWLAB 负责自身产品和接入层,包括用户鉴权、Cloud Web/CLI 对外 HWLAB 通过 AgentRun 执行 Code Agent turn 时,失败归因必须以 AgentRun backend adapter 的结构化 failure kind 为准。AgentRun 负责把 provider、thread、runner、bundle 和 command lifecycle 的失败分类成稳定语义;HWLAB 负责原样消费并映射到用户可读分类。不得为了让 UI 或 issue 收口看起来更顺,把 AgentRun/provider 错误改写成 device-pod、gateway、Cloud API endpoint 或前端渲染问题。 -Codex thread resume 失败必须由 AgentRun 明确归因和处理。当 `thread/resume` 遇到旧 app-server rollout 缺失、返回 `no rollout found for thread id` 这类可判定的 stale thread 时,AgentRun 应输出 `thread/resume:non-resumable`,启动 replacement `thread/start` 继续当前 turn,并在成功后回写新的 `threadId` 到 sessionRef;不得让用户轮次直接失败,也不得要求 HWLAB 通过清会话、隐藏错误或重开路径迁就。只有不可恢复的 resume 协议错误或 replacement 也失败时,才输出 `thread-resume-failed`。HWLAB 收到该 failure kind 时,应显示为 AgentRun thread resume/replacement 层错误,不要把它解释成硬件执行通道或 Cloud API 不可达。 +Codex thread resume 失败必须是单一路径失败。当 `thread/resume` 遇到旧 app-server rollout 缺失、返回 `no rollout found for thread id` 这类可判定的 stale thread 时,AgentRun 应终止当前 turn 并输出 `thread-resume-failed`,不得启动替代 `thread/start`、替换 session 指针或在同一轮混入第二条 thread 路径。HWLAB 收到该 failure kind 时,应显示为 AgentRun thread resume 失败和当前轮次终止;不要把它解释成硬件执行通道或 Cloud API 不可达,也不要通过清会话、隐藏错误或重开路径迁就。 Codex app-server/provider 返回 tool-call 参数 JSON 错误时,AgentRun 应输出 `provider-invalid-tool-call`。HWLAB adapter/Web 应映射为 provider/tool-call 层错误,并保留 `providerTrace.failureKind` 与简明 failure message,明确这不是 device-pod、gateway 或 Cloud API endpoint 故障。后续修复应进入 AgentRun provider/backend adapter 或上游 provider 请求构造,不要在 HWLAB 设备侧增加兼容路径。 diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 3f502d2e..36137a48 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -45,7 +45,7 @@ CI/CD、GitOps、rollout、artifact 发布、PR 合并后的 DEV/PROD 滚动、P - `hwlab g14 monitor-prs [--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 `ssh 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 指标。状态指针按用途分离:长期监控只写 `.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` 后再使用。 - `agentrun v01 control-plane status|trigger-current|refresh [--dry-run|--confirm]` 是 AgentRun `v0.1` 在 G14 k3s 的受控 Tekton/Argo 入口。`status` 只读汇总固定 source worktree commit、对应 commit-pinned PipelineRun、GitOps latest、Argo Application、`agentrun-v01` workload、`planArtifacts.summary`、env image result 和 git mirror 摘要,并报告 Argo revision 是否对齐 `v0.1-gitops` latest;`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。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 结果;`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 命令彼此独立。 -- `hwlab g14 control-plane status|apply --lane v02 [--dry-run|--confirm]` 是 HWLAB `v0.2` 加法 lane 的受控 Tekton/Argo 控制面维护入口,source commit 只来自 G14 专用 bare repo `/root/hwlab-v02-cicd.git` 的 `refs/remotes/origin/v0.2`;`/root/hwlab-v02` 只作为人工开发和短连接源码工具 workspace 被观测,dirty/stale 状态必须输出为 isolated warning 而不能阻塞 CI/CD。该入口面向 branch `v0.2`、namespace `hwlab-ci` 和 Argo application `hwlab-g14-v02`;默认 `status` 只读汇总最新 source head 的 pipeline、RBAC/ServiceAccount、Argo、当前 commit PipelineRun、当前 PipelineRun 的 TaskRun 条件摘要、最近 PipelineRun 摘要、活跃 PipelineRun、遗留 v02 CronJob 清理状态、commit alignment,以及 19666/19667 的 Cloud Web 静态资源和 API live 探针。分支被后续提交推进后,要复查已完成 run 时使用 `status --lane v02 --pipeline-run hwlab-v02-ci-poll-`;已知完整 source SHA 但不想依赖最新 head 时使用 `status --lane v02 --source-commit `。定点 `status` 输出 `statusTarget.mode`,只检查指定 PipelineRun/source commit 的证据,不因为 `origin/v0.2` 后续推进而把历史 run 判为失败;默认不带定点参数时仍严格判定最新 source head alignment。TaskRun 摘要的 `performance` 字段会把超过 120s 的 build TaskRun 标为慢任务、超过 180s 标为 critical warning,用于暴露 env reuse/git mirror 命中率回归,但不作为阻断门禁;CI/CD 性能验收应同时看 `planArtifacts.summary`、`taskRuns.performance.warningCount` 和 PipelineRun duration,纯 CLI/文档或无 runtime 重建需求的后续提交应稳定表现为 `build=0 reuse=` 且无 build TaskRun warning,首次引入或切换 env image 时允许只构建必要 env image 一次。`webAssets` 必须直接给出 `readonly-rpc` 删除、sidebar/workspace/event panel 关键 CSS、`/app.js` 是否可读取和字节数、`/health/live` 与 API revision;`apiRevision` 是 cloud-api 服务自身 revision,Cloud Web 静态资源变更时允许它与 source commit 不同,不能把这种差异误判成 Cloud Web 未发布。默认只读取必要字段,禁止把完整 PipelineRun spec、Tekton 内联脚本、历史大对象或整份 CSS/HTML/JS 展开到默认输出;`apply` 先自动 fetch `/root/hwlab-v02-cicd.git` 并从 commit-pinned detached worktree 执行 render check,再经 `G14:k3s` server-side apply `tekton-v02/rbac.yaml`、`pipeline.yaml`、`argocd/project.yaml` 和 `argocd/application-v02.yaml`,confirmed apply 会删除遗留 v02 CronJob,但不会应用 runtime-v02 workload、Secret 或数据迁移。 +- `hwlab g14 control-plane status|apply --lane v02 [--dry-run|--confirm]` 是 HWLAB `v0.2` 加法 lane 的受控 Tekton/Argo 控制面维护入口,source commit 只来自 G14 专用 bare repo `/root/hwlab-v02-cicd.git` 的 `refs/remotes/origin/v0.2`;`/root/hwlab-v02` 只作为人工开发和短连接源码工具 workspace 被观测,dirty/stale 状态必须输出为 isolated warning 而不能阻塞 CI/CD。该入口面向 branch `v0.2`、namespace `hwlab-ci` 和 Argo application `hwlab-g14-v02`;默认 `status` 只读汇总最新 source head 的 pipeline、RBAC/ServiceAccount、Argo、当前 commit PipelineRun、当前 PipelineRun 的 TaskRun 条件摘要、最近 PipelineRun 摘要、活跃 PipelineRun、遗留 v02 CronJob 清理状态、commit alignment,以及 19666/19667 的 Cloud Web 静态资源和 API live 探针。分支被后续提交推进后,要复查已完成 run 时使用 `status --lane v02 --pipeline-run hwlab-v02-ci-poll-`;已知完整 source SHA 但不想依赖最新 head 时使用 `status --lane v02 --source-commit `。定点 `status` 输出 `statusTarget.mode` 和 `targetValidation`,只检查指定 PipelineRun/source commit 的证据;`targetValidation.state=passed` 表示该目标已满足 PipelineRun succeeded、Argo `Synced/Healthy`、19666/19667 探针、Git mirror flushed、`hwlab-cloud-api`/`hwlab-cloud-web` runtime source commit 对齐;`targetValidation.state=superseded` 表示该目标已成功且 runtime 已被同一分支后续成功 PipelineRun 取代,`falseGreenGuard` 在该状态下应标为 superseded/not-applicable。两种状态都不得因为 `origin/v0.2` 后续推进而把历史 run 判为失败;默认不带定点参数时仍严格判定最新 source head alignment。TaskRun 摘要的 `performance` 字段会把超过 120s 的 build TaskRun 标为慢任务、超过 180s 标为 critical warning,用于暴露 env reuse/git mirror 命中率回归,但不作为阻断门禁;CI/CD 性能验收应同时看 `planArtifacts.summary`、`taskRuns.performance.warningCount` 和 PipelineRun duration,纯 CLI/文档或无 runtime 重建需求的后续提交应稳定表现为 `build=0 reuse=` 且无 build TaskRun warning,首次引入或切换 env image 时允许只构建必要 env image 一次。`webAssets` 必须直接给出 `readonly-rpc` 删除、sidebar/workspace/event panel 关键 CSS、`/app.js` 是否可读取和字节数、`/health/live` 与 API revision;`apiRevision` 是 cloud-api 服务自身 revision,Cloud Web 静态资源变更时允许它与 source commit 不同,不能把这种差异误判成 Cloud Web 未发布。默认只读取必要字段,禁止把完整 PipelineRun spec、Tekton 内联脚本、历史大对象或整份 CSS/HTML/JS 展开到默认输出;`apply` 先自动 fetch `/root/hwlab-v02-cicd.git` 并从 commit-pinned detached worktree 执行 render check,再经 `G14:k3s` server-side apply `tekton-v02/rbac.yaml`、`pipeline.yaml`、`argocd/project.yaml` 和 `argocd/application-v02.yaml`,confirmed apply 会删除遗留 v02 CronJob,但不会应用 runtime-v02 workload、Secret 或数据迁移。 - `hwlab g14 control-plane trigger-current --lane v02 [--dry-run|--confirm]` 是 v02 标准手动触发入口:先自动 fetch `/root/hwlab-v02-cicd.git`,解析当前 `origin/v0.2` full SHA,创建 commit-pinned `hwlab-v02-ci-poll-` PipelineRun;读 Git 走 `git-mirror-http.devops-infra.svc.cluster.local`,GitOps promotion 写 `git-mirror-write.devops-infra.svc.cluster.local`;confirmed trigger 在删除/创建 PipelineRun 前会先按当前 source commit 在 G14 临时 detached worktree 中 render,再 server-side apply v02 Tekton RBAC、Pipeline 与 Argo Application,避免 CI/CD 脚本或 runtime-ready 逻辑已合并但集群仍执行旧 Pipeline 定义;该 render 不要求固定 `/root/hwlab-v02` 工作树 clean,也不得因 `.worktree/` 或其他并行未提交修改阻塞;同名 PipelineRun 成功或运行中时拒绝重复触发,失败或不存在时才删除旧对象并重新创建。 创建 PipelineRun 前会读取 `devops-infra` mirror refs,若 `localV02` 未等于当前 source commit,则自动执行一次受控 manual `git-mirror sync` Job 并复核 ref,复核失败时停止触发,避免 Tekton `prepare-source` 已知失败;services 参数只包含 v02 runtime service matrix,`hwlab-cli` 是固定 repo 短连接源码工具,不进入 PipelineRun service build。 `--dry-run` 只报告是否会 pre-sync,不创建 Job;confirmed trigger 默认创建 `.state/jobs/` 异步 job 并立刻返回 `job.id`、`statusCommand`、stdout/stderr 路径,避免 git mirror pre-sync 或 PipelineRun 创建期间长时间阻塞;`--wait` 路径也必须向 stderr 输出 `hwlab.v02.trigger.progress` JSON 事件,覆盖 `control-plane-refresh`、`git-mirror-pre-sync`、`delete-existing-pipelinerun` 和 `create-pipelinerun`,避免异步 job 长时间只有启动命令而无法判断卡点;默认 JSON 必须对 `manifest_b64`、长脚本和远端 stdout/stderr 做有界摘要,保留长度与 hash,最终 trigger 结果只返回阶段摘要和关键 tail,完整内容通过 job stdout/stderr 文件渐进披露;只有现场同步调试才显式加 `--wait`;旧 `rerun-current` 只作为输入别名保留。PipelineRun `Completed`、Argo `Synced/Healthy` 和 `webAssets.ok=true` 只证明 G14 runtime 已更新;交付收口还必须用 `hwlab g14 git-mirror status` 查看 `cache.summary.pendingFlush`,若为 true,继续执行受控 `hwlab g14 git-mirror flush --confirm` 并用 job status 轮询到 `pendingFlush=false`。 diff --git a/docs/reference/g14.md b/docs/reference/g14.md index d6946d70..b5384939 100644 --- a/docs/reference/g14.md +++ b/docs/reference/g14.md @@ -71,6 +71,15 @@ The `devops-infra` git mirror/relay remains manual and CLI-controlled, not CronJ After a `v0.2` PipelineRun completes, treat runtime rollout and remote GitOps persistence as two separate checks. `hwlab g14 control-plane status --lane v02` is the runtime check: it must show the expected source commit, PipelineRun completed, Argo `Synced/Healthy`, public 19666/19667 probes passing, and Cloud Web asset probes such as `/app.js` readable. `hwlab g14 git-mirror status` is the persistence check: `cache.summary.pendingFlush` must be false and `cache.summary.githubInSync` true before declaring GitOps fully flushed back to GitHub. If runtime is healthy but `pendingFlush=true`, run `bun scripts/cli.ts hwlab g14 git-mirror flush --confirm` and poll the returned job with `bun scripts/cli.ts job status --tail-bytes 12000`; do not replace this with raw `kubectl`, native `git push`, or a long SSH wait. +When closing an issue against a specific completed `v0.2` PipelineRun, use targeted status instead of the latest-head status if `origin/v0.2` has already advanced through a parallel task: + +```bash +bun scripts/cli.ts hwlab g14 control-plane status --lane v02 --pipeline-run hwlab-v02-ci-poll- +bun scripts/cli.ts hwlab g14 control-plane status --lane v02 --source-commit +``` + +Targeted status must expose `statusTarget.mode` and `targetValidation`. `targetValidation.state=passed` means the requested PipelineRun/source commit reached a succeeded PipelineRun, Argo `Synced/Healthy`, public web/API probes, flushed Git mirror, and matching `hwlab-cloud-api` / `hwlab-cloud-web` runtime source commits. `targetValidation.state=superseded` means the requested PipelineRun succeeded and was later replaced in runtime by a newer succeeded `v0.2` PipelineRun; this is valid closure evidence for the requested run when the newer commit is on the same branch lineage. In both states, `commitAlignment.staleReasons` may still mention later `origin/v0.2` or CI/CD source head movement; that is parallel-head context, not a failure of the requested run. `falseGreenGuard` is a current-runtime guard and should report not-applicable/superseded for such historical targets instead of turning later runtime movement into a false failure. Default status without a target remains strict for the latest source head. + `/health/live` revision is owned by `hwlab-cloud-api`; it can legitimately differ from the source commit for a Cloud Web-only change. Do not call that difference a failed Cloud Web rollout when `webAssets.checks.htmlOk`, `webAssets.checks.appJsOk`, CSS probes, Argo health, and `hwlab-cloud-web` Deployment readiness have passed. For Cloud Web behavior changes, the public JS asset probe or a bounded browser/DOM check is stronger evidence than cloud-api `apiRevision`. Do not turn `v0.2` expansion governance into a stack of broad compatibility gates. The stable control points are branch, dedicated CI/CD source repo, git mirror/relay refs, GitOps branch, namespace, runtime path, Argo Application, FRP ports and generated-output ownership. Legacy DEV/D601/main preflights that block the `v0.2` lane should be removed from that lane, not patched with fallback or legacy modes. Naming, RBAC scope, cleanup policy, resource quota and rollback order are design decisions or runbook entries unless they protect a concrete high-value risk that cannot be enforced by the fixed boundaries above. diff --git a/scripts/src/hwlab-g14.ts b/scripts/src/hwlab-g14.ts index 50daee2b..93ef7379 100644 --- a/scripts/src/hwlab-g14.ts +++ b/scripts/src/hwlab-g14.ts @@ -1233,6 +1233,101 @@ export function v02FalseGreenGuard(input: { }; } +function v02TargetValidation(input: { + targetMode: V02StatusTargetMode; + sourceCommit: string | null; + pipelineRun: Record | null; + argo: Record; + runtimeWorkloads: Record; + webAssets: Record; + gitMirror: Record; + recentPipelineRuns: Record; +}): Record { + if (input.targetMode === "latest-source-head") { + return { + applicable: false, + state: "latest-source-head", + summary: "default status uses strict latest source head alignment; use --pipeline-run or --source-commit for historical run validation", + }; + } + const sourceCommit = input.sourceCommit; + const argoFields = record(input.argo.fields); + const gitMirrorSummary = record(input.gitMirror.summary); + const workloadItems = Array.isArray(input.runtimeWorkloads.items) + ? input.runtimeWorkloads.items.map((item) => record(item)) + : []; + const serviceSourceCommits = Object.fromEntries(workloadItems + .filter((item) => item.serviceId === "hwlab-cloud-api" || item.serviceId === "hwlab-cloud-web") + .map((item) => [String(item.serviceId), stringOrNull(item.sourceCommit) ?? stringOrNull(item.artifactSourceCommit)])); + const recentItems = Array.isArray(input.recentPipelineRuns.items) + ? input.recentPipelineRuns.items.map((item) => record(item)) + : []; + const targetPipelineRunName = stringOrNull(input.pipelineRun?.pipelineRun) ?? stringOrNull(input.pipelineRun?.name); + const targetRecentIndex = recentItems.findIndex((item) => ( + (targetPipelineRunName !== null && item.name === targetPipelineRunName) + || (sourceCommit !== null && item.sourceCommit === sourceCommit) + )); + const newerCandidates = targetRecentIndex >= 0 ? recentItems.slice(0, targetRecentIndex) : recentItems; + const newerSucceededRuns = sourceCommit === null + ? [] + : newerCandidates.filter((item) => item.sourceCommit !== sourceCommit && item.status === "True"); + const failures: Record[] = []; + const supersededRuntimeServices: Record[] = []; + if (sourceCommit === null) failures.push({ reason: "source-commit-unresolved" }); + if (input.pipelineRun === null || input.pipelineRun.exists === false) { + failures.push({ reason: "pipeline-run-missing" }); + } else if (input.pipelineRun.status !== "True") { + failures.push({ reason: "pipeline-run-not-succeeded", status: input.pipelineRun.status ?? null, reasonDetail: input.pipelineRun.reason ?? null }); + } + if (input.argo.ok !== true || argoFields.syncStatus !== "Synced" || argoFields.health !== "Healthy") { + failures.push({ reason: "argo-not-synced-healthy", syncStatus: argoFields.syncStatus ?? null, health: argoFields.health ?? null }); + } + if (input.webAssets.ok !== true) failures.push({ reason: "web-assets-probe-failed", summary: input.webAssets.summary ?? null }); + if (gitMirrorSummary.pendingFlush !== false || gitMirrorSummary.githubInSync !== true) { + failures.push({ + reason: "git-mirror-not-flushed", + pendingFlush: gitMirrorSummary.pendingFlush ?? null, + githubInSync: gitMirrorSummary.githubInSync ?? null, + }); + } + for (const serviceId of ["hwlab-cloud-api", "hwlab-cloud-web"]) { + const serviceCommit = serviceSourceCommits[serviceId]; + if (sourceCommit !== null && serviceCommit !== sourceCommit) { + const mismatch = { reason: "runtime-service-source-mismatch", serviceId, expectedSourceCommit: sourceCommit, actualSourceCommit: serviceCommit ?? null }; + if (newerSucceededRuns.length > 0) supersededRuntimeServices.push(mismatch); + else failures.push(mismatch); + } + } + const superseded = failures.length === 0 && supersededRuntimeServices.length > 0; + return { + applicable: true, + ok: failures.length === 0, + state: failures.length === 0 ? (superseded ? "superseded" : "passed") : "failed", + summary: failures.length === 0 + ? superseded + ? `target ${input.targetMode} completed for ${shortSha(sourceCommit ?? "")}; runtime now reflects newer v0.2 PipelineRun` + : `target ${input.targetMode} validation passed for ${shortSha(sourceCommit ?? "")}` + : `target ${input.targetMode} validation failed with ${failures.length} issue(s)`, + sourceCommit, + pipelineRun: input.pipelineRun === null + ? null + : { name: input.pipelineRun.pipelineRun ?? null, status: input.pipelineRun.status ?? null, reason: input.pipelineRun.reason ?? null }, + argo: { syncRevision: argoFields.syncRevision ?? null, syncStatus: argoFields.syncStatus ?? null, health: argoFields.health ?? null }, + runtimeServices: serviceSourceCommits, + superseded, + supersededRuntimeServices, + newerSucceededRuns: newerSucceededRuns.slice(0, 3).map((item) => ({ + name: item.name ?? null, + sourceCommit: item.sourceCommit ?? null, + createdAt: item.createdAt ?? null, + durationSeconds: item.durationSeconds ?? null, + })), + webAssets: { ok: input.webAssets.ok ?? null, summary: input.webAssets.summary ?? null, appJsBytes: record(input.webAssets.assetBytes).appJs ?? null }, + gitMirror: { pendingFlush: gitMirrorSummary.pendingFlush ?? null, githubInSync: gitMirrorSummary.githubInSync ?? null }, + failures, + }; +} + export function v02CommitAlignment(input: { expectedSourceHead: string | null; sourceHeads: Record; @@ -1939,6 +2034,30 @@ function v02ControlPlaneStatus(target: V02ControlPlaneStatusTarget = {}): Record runtimeWorkloads, webAssets, }); + const targetValidation = v02TargetValidation({ + targetMode, + sourceCommit, + pipelineRun: pipelineRunInfo, + argo: { + ok: shellSectionOk(argo), + fields: { targetRevision, path, syncRevision, syncStatus, health }, + }, + runtimeWorkloads, + webAssets, + gitMirror, + recentPipelineRuns, + }); + const falseGreenGuard = targetValidation.state === "superseded" + ? { + ok: null, + state: "superseded", + summary: "target PipelineRun succeeded but runtime now reflects a newer v0.2 PipelineRun; current-runtime false-green guard is not applicable to this historical target", + sourceCommit, + targetValidationState: targetValidation.state, + newerSucceededRuns: targetValidation.newerSucceededRuns ?? [], + supersededRuntimeServices: targetValidation.supersededRuntimeServices ?? [], + } + : v02FalseGreenGuard({ sourceCommit, pipelineRun: pipelineRunInfo, taskRuns, planArtifacts, runtimeWorkloads }); const baseOk = sourceCommit !== null && isCommandSuccess(bundle) && shellSectionOk(controlPlane) && shellSectionOk(argo); const targetPipelineRunOk = strictHeadAlignment ? true @@ -1984,6 +2103,7 @@ function v02ControlPlaneStatus(target: V02ControlPlaneStatusTarget = {}): Record argoApplication: V02_APP, }, commitAlignment, + targetValidation, sourceHeads, gitMirror, controlPlane: { @@ -2007,7 +2127,7 @@ function v02ControlPlaneStatus(target: V02ControlPlaneStatusTarget = {}): Record taskRuns, planArtifacts, runtimeWorkloads, - falseGreenGuard: v02FalseGreenGuard({ sourceCommit, pipelineRun: pipelineRunInfo, taskRuns, planArtifacts, runtimeWorkloads }), + falseGreenGuard, webAssets, activePipelineRuns, recentPipelineRuns,