diff --git a/README.md b/README.md index 454008e..c764606 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ - 生成改动文档同步计划:按变更文件自动提示需要同步的 README、API、AGENTS、docs、Obsidian、skill 和 ktWorkflow 入口。 - 生成多仓库提交/推送计划:按仓库分组、建议提交信息、列出提交和推送前检查。 - 生成远程只读健康检查和数据库同步安全向导:覆盖飞牛 NAS 服务探测、GTID、`.kt-workspace/db-sync` 转储、备份库和行数校验。 -- 生成或执行部署观测:把 Jenkins build、`build.xml` SCM revision、日志尾部、K8s Deployment、Pod、`/health/runtime` 和任务 smoke 汇总成 `.kt-workspace/test-artifacts/deploy-observation` 下的运行态证据。 +- 生成或执行部署观测:把 Jenkins build、`build.xml` SCM revision、日志尾部、K8s Deployment、Pod、`/health/runtime` 和任务 smoke 汇总成 `.kt-workspace/test-artifacts/deploy-observation` 下的运行态证据;Jenkins 状态以日志尾部最终 `Finished:` 为准,日志存在但缺少最终态时不认定发布完成。 - 生成专项组件工作流:KtTable、BlogArgon、AdminAuth、QQBot、FF14Plugin、NapCatLogin、SystemLog、Knife4jSwagger、FnosK8s 的防踩坑清单和验证点。 - 生成验证进程清理计划:按项目路径和端口给出 PowerShell 检查命令,不直接杀进程。 - 清理历史产物:统一治理 `.kt-workspace` 下的测试/验证产物,按目录最近修改时间只保留最近 3 轮,模板目录永久保留;CLI 默认 dry-run,真实清理必须显式传 `--execute`。 diff --git a/src/selfTest.ts b/src/selfTest.ts index 38600ac..3930a65 100644 --- a/src/selfTest.ts +++ b/src/selfTest.ts @@ -443,7 +443,9 @@ export async function runSelfTest(): Promise { desiredReplicas?: number; healthStatus?: string; jenkinsBuildXml?: string; + logFinishedStatus?: string; logCommit?: string; + omitFinishedStatus?: boolean; omitSmoke?: boolean; readyReplicas?: number; smokeExitCode?: number; @@ -457,9 +459,11 @@ export async function runSelfTest(): Promise { "result=SUCCESS", "__KT_SECTION:jenkins_log_tail__", overrides.logCommit === "" - ? "Finished: SUCCESS" + ? `Finished: ${overrides.logFinishedStatus ?? "SUCCESS"}` : `Checking out Revision ${overrides.logCommit ?? "abc1234"}`, - "Finished: SUCCESS", + ...(overrides.omitFinishedStatus + ? [] + : [`Finished: ${overrides.logFinishedStatus ?? "SUCCESS"}`]), "__KT_SECTION:jenkins_build_xml__", overrides.jenkinsBuildXml ?? "", "__KT_SECTION:deployment_json__", @@ -603,6 +607,38 @@ export async function runSelfTest(): Promise { throw new Error("deploy observation build.xml commit self-check failed"); } + const deployObservationLogFailureBeatsXmlEvidence = + normalizeDeployObservationEvidence({ + checkedAt: new Date("2026-06-14T00:00:02.000Z"), + input: deployObservationInput, + sectionMap: buildDeployObservationTestSections({ + logFinishedStatus: "FAILURE", + }), + }); + if ( + deployObservationLogFailureBeatsXmlEvidence.status === "passed" || + deployObservationLogFailureBeatsXmlEvidence.details.jenkins + .finishedStatus !== "FAILURE" + ) { + throw new Error("deploy observation log failure precedence self-check failed"); + } + + const deployObservationRunningLogWithoutFinalStatusEvidence = + normalizeDeployObservationEvidence({ + checkedAt: new Date("2026-06-14T00:00:02.000Z"), + input: deployObservationInput, + sectionMap: buildDeployObservationTestSections({ + omitFinishedStatus: true, + }), + }); + if ( + deployObservationRunningLogWithoutFinalStatusEvidence.status === "passed" || + deployObservationRunningLogWithoutFinalStatusEvidence.details.jenkins + .finishedStatus !== null + ) { + throw new Error("deploy observation running log status self-check failed"); + } + const deployObservationFailedSmokeEvidence = normalizeDeployObservationEvidence({ checkedAt: new Date("2026-06-14T00:00:02.000Z"), diff --git a/src/tools/deployObservation.ts b/src/tools/deployObservation.ts index 875cca3..f5febc4 100644 --- a/src/tools/deployObservation.ts +++ b/src/tools/deployObservation.ts @@ -404,10 +404,14 @@ function normalizeJenkinsEvidence( const logTail = sectionMap.get('jenkins_log_tail') || ''; const buildXmlRevision = sectionMap.get('jenkins_build_xml') || ''; const commitHaystack = `${logTail}\n${buildXmlRevision}`.toLowerCase(); + const logFinishedStatuses = [...logTail.matchAll(/Finished:\s*([A-Z_]+)/gi)]; + const logFinishedStatus = + logFinishedStatuses.at(-1)?.[1]?.toUpperCase() || null; + const logTailAvailable = + logTail.trim().length > 0 && !/jenkins log not found:/i.test(logTail); const finishedStatus = - meta.result || - logTail.match(/Finished:\s*([A-Z_]+)/i)?.[1]?.toUpperCase() || - null; + logFinishedStatus || + (logTailAvailable ? null : meta.result?.toUpperCase() || null); const expectedCommit = input.expectedCommit || null; return {