fix: 修复Jenkins发布观测状态回退

This commit is contained in:
sunlei 2026-07-04 10:33:14 +08:00
parent 7324ce5bae
commit 223a4d1d62
3 changed files with 39 additions and 5 deletions

View File

@ -21,7 +21,7 @@
- 生成改动文档同步计划:按变更文件自动提示需要同步的 README、API、AGENTS、docs、Obsidian、skill 和 ktWorkflow 入口MCP 不可用时用 `pnpm run change-doc-sync -- --project <project>` 作为 CLI fallback。 - 生成改动文档同步计划:按变更文件自动提示需要同步的 README、API、AGENTS、docs、Obsidian、skill 和 ktWorkflow 入口MCP 不可用时用 `pnpm run change-doc-sync -- --project <project>` 作为 CLI fallback。
- 生成多仓库提交/推送计划:按仓库分组、建议提交信息、列出提交和推送前检查。 - 生成多仓库提交/推送计划:按仓库分组、建议提交信息、列出提交和推送前检查。
- 生成远程只读健康检查和数据库同步安全向导:覆盖飞牛 NAS 服务探测、GTID、`.kt-workspace/db-sync` 转储、备份库和行数校验。 - 生成远程只读健康检查和数据库同步安全向导:覆盖飞牛 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 状态以日志尾部最终 `Finished:` 为准,日志存在但缺少最终态时不认定发布完成。 - 生成或执行部署观测:把 Jenkins build、`build.xml` SCM revision/result、日志尾部、K8s Deployment、Pod、`/health/runtime` 和任务 smoke 汇总成 `.kt-workspace/test-artifacts/deploy-observation` 下的运行态证据Jenkins 状态优先取日志尾部最终 `Finished:`,没有最终行时回退到 `build.xml` 的最终 resultresult 为空才视为未完成。
- 生成专项组件工作流KtTable、BlogArgon、AdminAuth、QQBot、FF14Plugin、NapCatLogin、SystemLog、Knife4jSwagger、FnosK8s 的防踩坑清单和验证点。 - 生成专项组件工作流KtTable、BlogArgon、AdminAuth、QQBot、FF14Plugin、NapCatLogin、SystemLog、Knife4jSwagger、FnosK8s 的防踩坑清单和验证点。
- 生成验证进程清理计划:按项目路径和端口给出 PowerShell 检查命令,不直接杀进程。 - 生成验证进程清理计划:按项目路径和端口给出 PowerShell 检查命令,不直接杀进程。
- 清理历史产物:统一治理 `.kt-workspace` 下的测试/验证产物,按目录最近修改时间只保留最近 3 轮模板目录永久保留CLI 默认 dry-run真实清理必须显式传 `--execute` - 清理历史产物:统一治理 `.kt-workspace` 下的测试/验证产物,按目录最近修改时间只保留最近 3 轮模板目录永久保留CLI 默认 dry-run真实清理必须显式传 `--execute`

View File

@ -1604,10 +1604,18 @@ export async function runSelfTest(): Promise<void> {
throw new Error("deploy observation CLI parser self-check failed"); throw new Error("deploy observation CLI parser self-check failed");
} }
/**
* Builds deploy-observation fixture sections with small overrides for the
* parser branches that need deterministic regression coverage.
*
* @param overrides - Optional section mutations for Jenkins, Kubernetes, runtime health, and task smoke evidence.
* @returns Parsed section map matching the SSH collector output format.
*/
const buildDeployObservationTestSections = (overrides: { const buildDeployObservationTestSections = (overrides: {
desiredReplicas?: number; desiredReplicas?: number;
healthStatus?: string; healthStatus?: string;
jenkinsBuildXml?: string; jenkinsBuildXml?: string;
jenkinsResult?: string;
logFinishedStatus?: string; logFinishedStatus?: string;
logCommit?: string; logCommit?: string;
omitFinishedStatus?: boolean; omitFinishedStatus?: boolean;
@ -1621,7 +1629,7 @@ export async function runSelfTest(): Promise<void> {
buildDeployObservationSectionMap([ buildDeployObservationSectionMap([
"__KT_SECTION:jenkins_meta__", "__KT_SECTION:jenkins_meta__",
"buildNumber=132", "buildNumber=132",
"result=SUCCESS", `result=${overrides.jenkinsResult ?? "SUCCESS"}`,
"__KT_SECTION:jenkins_log_tail__", "__KT_SECTION:jenkins_log_tail__",
overrides.logCommit === "" overrides.logCommit === ""
? `Finished: ${overrides.logFinishedStatus ?? "SUCCESS"}` ? `Finished: ${overrides.logFinishedStatus ?? "SUCCESS"}`
@ -1772,6 +1780,25 @@ export async function runSelfTest(): Promise<void> {
throw new Error("deploy observation build.xml commit self-check failed"); throw new Error("deploy observation build.xml commit self-check failed");
} }
const deployObservationBuildXmlResultWithoutLogFinishedEvidence =
normalizeDeployObservationEvidence({
checkedAt: new Date("2026-06-14T00:00:02.000Z"),
input: deployObservationInput,
sectionMap: buildDeployObservationTestSections({
omitFinishedStatus: true,
}),
});
if (
deployObservationBuildXmlResultWithoutLogFinishedEvidence.status !==
"passed" ||
deployObservationBuildXmlResultWithoutLogFinishedEvidence.details.jenkins
.finishedStatus !== "SUCCESS"
) {
throw new Error(
"deploy observation build.xml result fallback self-check failed",
);
}
const deployObservationLogFailureBeatsXmlEvidence = const deployObservationLogFailureBeatsXmlEvidence =
normalizeDeployObservationEvidence({ normalizeDeployObservationEvidence({
checkedAt: new Date("2026-06-14T00:00:02.000Z"), checkedAt: new Date("2026-06-14T00:00:02.000Z"),
@ -1793,6 +1820,7 @@ export async function runSelfTest(): Promise<void> {
checkedAt: new Date("2026-06-14T00:00:02.000Z"), checkedAt: new Date("2026-06-14T00:00:02.000Z"),
input: deployObservationInput, input: deployObservationInput,
sectionMap: buildDeployObservationTestSections({ sectionMap: buildDeployObservationTestSections({
jenkinsResult: "",
omitFinishedStatus: true, omitFinishedStatus: true,
}), }),
}); });

View File

@ -396,6 +396,13 @@ export function buildDeployObservationSectionMap(
return sections; return sections;
} }
/**
* Normalizes Jenkins build evidence collected from the NAS filesystem.
*
* @param input - Normalized deploy observation request, including the expected job and commit.
* @param sectionMap - SSH output sections containing Jenkins metadata, build.xml snippets, and log tail.
* @returns Jenkins build identity, completion status, and commit-match evidence for closeout assertions.
*/
function normalizeJenkinsEvidence( function normalizeJenkinsEvidence(
input: NormalizedInput, input: NormalizedInput,
sectionMap: Map<string, string>, sectionMap: Map<string, string>,
@ -407,11 +414,10 @@ function normalizeJenkinsEvidence(
const logFinishedStatuses = [...logTail.matchAll(/Finished:\s*([A-Z_]+)/gi)]; const logFinishedStatuses = [...logTail.matchAll(/Finished:\s*([A-Z_]+)/gi)];
const logFinishedStatus = const logFinishedStatus =
logFinishedStatuses.at(-1)?.[1]?.toUpperCase() || null; logFinishedStatuses.at(-1)?.[1]?.toUpperCase() || null;
const logTailAvailable =
logTail.trim().length > 0 && !/jenkins log not found:/i.test(logTail);
const finishedStatus = const finishedStatus =
logFinishedStatus || logFinishedStatus ||
(logTailAvailable ? null : meta.result?.toUpperCase() || null); meta.result?.toUpperCase() ||
null;
const expectedCommit = input.expectedCommit || null; const expectedCommit = input.expectedCommit || null;
return { return {