import { gitMirrorFlushJobManifest, gitMirrorStatusSummary, gitMirrorSyncJobManifest, gitMirrorV02SyncRequirement, hwlabG14Help, hwlabG14MonitorStateFileName, parseGitMirrorStatusRefs, parsePipelineTaskRunMetrics, rolloutRecordBody, semanticChangelogBullets, v02CommitAlignment, v02ControlPlaneRenderScript, v02FalseGreenGuard, v02PipelineServiceIds, v02TaskRunPerformanceSummary } from "./src/hwlab-g14"; function assertCondition(condition: unknown, message: string, detail: unknown = {}): void { if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`); } function record(value: unknown): Record { return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record : {}; } assertCondition( hwlabG14MonitorStateFileName({ once: false, dryRun: false }) === "latest-monitor-job.json", "long-running monitor should own latest-monitor-job.json", ); assertCondition( hwlabG14MonitorStateFileName({ once: true, dryRun: false }) === "latest-once-job.json", "once jobs must not overwrite the long-running monitor pointer", ); assertCondition( hwlabG14MonitorStateFileName({ once: false, dryRun: true }) === "latest-dry-run-job.json", "dry-run monitor jobs must not overwrite the live monitor pointer", ); assertCondition( hwlabG14MonitorStateFileName({ once: true, dryRun: true }) === "latest-once-dry-run-job.json", "once dry-runs need a distinct pointer for low-noise diagnostics", ); const hwlabHelpUsage = Array.isArray(hwlabG14Help().usage) ? hwlabG14Help().usage.map(String) : []; assertCondition( hwlabHelpUsage.some((line) => line.includes("control-plane status --lane v02 --pipeline-run")) && hwlabHelpUsage.some((line) => line.includes("control-plane status --lane v02 --source-commit")), "v0.2 control-plane status help must expose targeted PipelineRun/source-commit inspection", hwlabHelpUsage, ); const gitMirrorJob = gitMirrorSyncJobManifest("git-mirror-hwlab-sync-manual-test"); assertCondition(gitMirrorJob.kind === "Job", "git mirror sync must be a manual Job, not a CronJob", gitMirrorJob); assertCondition(record(gitMirrorJob.metadata).namespace === "devops-infra", "git mirror sync Job must target devops-infra", gitMirrorJob); assertCondition(record(record(gitMirrorJob.metadata).labels)["hwlab.pikastech.local/trigger"] === "manual-cli", "git mirror sync Job must be labeled as manual CLI triggered", gitMirrorJob); assertCondition(record(gitMirrorJob.spec).backoffLimit === 0, "git mirror sync Job should fail visibly instead of retrying in the background", gitMirrorJob); const gitMirrorFlushJob = gitMirrorFlushJobManifest("git-mirror-hwlab-flush-manual-test"); assertCondition(gitMirrorFlushJob.kind === "Job", "git mirror flush must be a manual Job", gitMirrorFlushJob); assertCondition(record(record(gitMirrorFlushJob.metadata).labels)["app.kubernetes.io/component"] === "flush-controller", "git mirror flush Job must be labeled separately from sync", gitMirrorFlushJob); assertCondition(record(record(gitMirrorFlushJob.metadata).labels)["hwlab.pikastech.local/trigger"] === "manual-cli", "git mirror flush Job must be labeled as manual CLI triggered", gitMirrorFlushJob); const flushTemplate = record(record(gitMirrorFlushJob.spec).template); const flushPodSpec = record(flushTemplate.spec); const flushContainer = record(Array.isArray(flushPodSpec.containers) ? flushPodSpec.containers[0] : null); assertCondition(JSON.stringify(flushContainer.command) === JSON.stringify(["/script/flush.sh"]), "git mirror flush Job must run the flush script", gitMirrorFlushJob); const gitMirrorStatusRaw = [ 'lastSync={"ok":true}', "lastWrite=", "lastFlush=", 'refs={"refs":{"localV02":"0cf79ecc9d8a784b7712b0b3a58d5e39025ba0dc","githubV02":"0cf79ecc9d8a784b7712b0b3a58d5e39025ba0dc","localG14":"1111111111111111111111111111111111111111","githubG14":"1111111111111111111111111111111111111111","localGitops":"2222222222222222222222222222222222222222","githubGitops":"3333333333333333333333333333333333333333"},"pendingFlush":true}', ].join("\n"); const parsedGitMirrorRefs = parseGitMirrorStatusRefs(gitMirrorStatusRaw); assertCondition( parsedGitMirrorRefs.refs.localV02 === "0cf79ecc9d8a784b7712b0b3a58d5e39025ba0dc", "git mirror status parser must expose local v0.2 ref for trigger pre-sync", parsedGitMirrorRefs, ); assertCondition(parsedGitMirrorRefs.pendingFlush === true, "git mirror status parser must preserve pending flush signal", parsedGitMirrorRefs); assertCondition( parsedGitMirrorRefs.refs.githubV02 === "0cf79ecc9d8a784b7712b0b3a58d5e39025ba0dc", "git mirror status parser must expose GitHub source branch staging ref", parsedGitMirrorRefs, ); assertCondition( gitMirrorV02SyncRequirement("0cf79ecc9d8a784b7712b0b3a58d5e39025ba0dc", gitMirrorStatusRaw).required === false, "trigger-current must not sync mirror when local v0.2 already matches source commit", ); assertCondition( gitMirrorV02SyncRequirement("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", gitMirrorStatusRaw).required === true, "trigger-current must sync mirror before creating PipelineRun when local v0.2 is stale", ); const gitMirrorSummary = gitMirrorStatusSummary(gitMirrorStatusRaw); assertCondition( gitMirrorSummary.flushNeeded === true && gitMirrorSummary.flushCommand === "bun scripts/cli.ts hwlab g14 git-mirror flush --confirm", "git mirror status summary must expose pending flush and the exact controlled flush command", gitMirrorSummary, ); assertCondition(gitMirrorSummary.githubInSync === false, "git mirror status summary must expose GitHub GitOps drift", gitMirrorSummary); assertCondition(gitMirrorSummary.sourceInSync === true && gitMirrorSummary.gitopsInSync === false, "git mirror status must split source and gitops GitHub sync state", gitMirrorSummary); const renderScript = v02ControlPlaneRenderScript("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); assertCondition( renderScript.includes("git --git-dir=\"$cicd_repo\" worktree add --detach") && renderScript.includes("/tmp/hwlab-v02-control-plane-source-aaaaaaaaaaaa"), "v0.2 control-plane render must use a detached temp worktree from the CI/CD repo", renderScript, ); assertCondition( renderScript.includes("cicd_repo='/root/hwlab-v02-cicd.git'") && !renderScript.includes("git -C /root/hwlab-v02") && !renderScript.includes("git checkout v0.2"), "v0.2 control-plane render must not use the fixed workspace checkout or its clean status", renderScript, ); assertCondition( !v02PipelineServiceIds().includes("hwlab-cli"), "v0.2 PipelineRun service matrix must not build hwlab-cli because cli is short-connection source tool", v02PipelineServiceIds(), ); const staleSuccessAlignment = v02CommitAlignment({ expectedSourceHead: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", sourceHeads: { cicdRepo: "/root/hwlab-v02-cicd.git", cicdSourceHead: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", originHead: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", workspace: { path: "/root/hwlab-v02", head: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", originHead: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", dirty: true, dirtyCount: 1 }, }, gitMirrorSummary: { localV02: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", githubV02: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", sourceInSync: false, gitopsInSync: true }, pipelineRun: { pipelineRun: "hwlab-v02-ci-poll-aaaaaaaaaaaa", status: null }, recentPipelineRuns: { items: [{ name: "hwlab-v02-ci-poll-bbbbbbbbbbbb", sourceCommit: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", status: "True", reason: "Completed" }] }, runtimeWorkloads: { items: [] }, webAssets: { apiRevision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }, }); assertCondition( staleSuccessAlignment.aligned === false && staleSuccessAlignment.state === "stale-success" && JSON.stringify(staleSuccessAlignment.staleReasons).includes("mirror-source-stale") && JSON.stringify(staleSuccessAlignment.workspaceWarnings).includes("workspace-dirty-but-isolated-from-cicd"), "v0.2 commit alignment must call out stale-success while keeping dirty workspace isolated from CI source selection", staleSuccessAlignment, ); const falseGreenPassed = v02FalseGreenGuard({ sourceCommit: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", pipelineRun: { exists: true, status: "True" }, taskRuns: { items: [ { name: "hwlab-v02-ci-poll-aaaaaaaaaaaa-build-hwlab-cloud-api", status: "True" }, { name: "hwlab-v02-ci-poll-aaaaaaaaaaaa-build-hwlab-cloud-web", status: "True" }, ], }, planArtifacts: { ok: true, buildServices: ["hwlab-cloud-api"], reusedServices: ["hwlab-cloud-web"], artifactProvenanceAudit: { ok: true, unsafeReuseServices: [], }, }, runtimeWorkloads: { ok: true, items: [ { serviceId: "hwlab-cloud-api", artifactSourceCommit: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }, { serviceId: "hwlab-cloud-web", artifactSourceCommit: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }, ], }, }); assertCondition( falseGreenPassed.ok === true && falseGreenPassed.state === "passed", "v0.2 false-green guard should pass when built runtime services and reuse provenance are proven", falseGreenPassed, ); const falseGreenReuseMissingAudit = v02FalseGreenGuard({ sourceCommit: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", pipelineRun: { exists: true, status: "True" }, taskRuns: { items: [{ name: "hwlab-v02-ci-poll-aaaaaaaaaaaa-build-hwlab-cloud-api", status: "True" }] }, planArtifacts: { ok: true, buildServices: ["hwlab-cloud-api"], reusedServices: ["hwlab-cloud-web"], artifactProvenanceAudit: null, }, runtimeWorkloads: { ok: true, items: [{ serviceId: "hwlab-cloud-api", artifactSourceCommit: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }] }, }); assertCondition( falseGreenReuseMissingAudit.ok === true && JSON.stringify(falseGreenReuseMissingAudit.provenanceWarnings).includes("artifact-provenance-audit-missing-for-reuse"), "v0.2 false-green guard should not fail a completed rollout only because optional reuse provenance is missing", falseGreenReuseMissingAudit, ); const falseGreenRuntimeMismatch = v02FalseGreenGuard({ sourceCommit: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", pipelineRun: { exists: true, status: "True" }, taskRuns: { items: [{ name: "hwlab-v02-ci-poll-aaaaaaaaaaaa-build-hwlab-cloud-api", status: "True" }] }, planArtifacts: { ok: true, buildServices: ["hwlab-cloud-api"], reusedServices: [], artifactProvenanceAudit: null, }, runtimeWorkloads: { ok: true, items: [{ serviceId: "hwlab-cloud-api", artifactSourceCommit: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }] }, }); assertCondition( falseGreenRuntimeMismatch.ok === false && JSON.stringify(falseGreenRuntimeMismatch.failures).includes("artifact-source-commit-mismatch"), "v0.2 false-green guard must fail when a built service still runs an old artifact", falseGreenRuntimeMismatch, ); const slowBuildSummary = v02TaskRunPerformanceSummary([ { name: "hwlab-v02-ci-poll-f8a090b66616-build-hwlab-agent-worker", status: "True", reason: "Succeeded", durationSeconds: 234, }, { name: "hwlab-v02-ci-poll-f8a090b66616-build-hwlab-cloud-web", status: "True", reason: "Succeeded", durationSeconds: 37, }, ]); const slowBuildItems = Array.isArray(record(slowBuildSummary).slowTaskRuns) ? record(slowBuildSummary).slowTaskRuns as Record[] : []; assertCondition( slowBuildSummary.ok === false && record(record(slowBuildSummary).thresholds).buildTaskRunWarningSeconds === 120 && slowBuildItems.length === 1 && slowBuildItems[0]?.serviceId === "hwlab-agent-worker" && slowBuildItems[0]?.severity === "critical", "v0.2 status must warn on slow build TaskRuns like issue #659", slowBuildSummary, ); const prBody = [ "## 背景", "", "G14 DEV Cloud Workbench 中,内部 `message-trace-body` 的 `pre` 会在 trace 更新时被替换,导致用户滚到中间后被重置到顶部。", "", "## 修改", "", "- trace row patch 从 index/replace 改为 stable `data-trace-row-id` keyed reconciliation。", "- 已存在 row 只移动和原地更新 header/body,不替换 `li/pre`。", "- `message-trace-body` 内部滚动状态按 `traceUiKey + rowId` 记忆。", ].join("\n"); const semanticBullets = semanticChangelogBullets("fix: preserve inner trace scroll", prBody, { keyFiles: [ "modified web/hwlab-cloud-web/app.mjs", "modified web/hwlab-cloud-web/scripts/trace-scroll.test.mjs", ], }); assertCondition( semanticBullets.some((line) => line.includes("修复目标") && line.includes("滚到中间后被重置到顶部")), "semantic changelog should explain the user-visible bug being fixed", semanticBullets, ); assertCondition( semanticBullets.some((line) => line.includes("data-trace-row-id")), "semantic changelog should include PR body change bullets instead of only file stats", semanticBullets, ); const summaryBullets = semanticChangelogBullets("feat: preload gateway tran helper", [ "## 摘要", "- 新增 `/app/tools/hwlab-gateway-tran.mjs`,支持 `cmd`、`ps`、`upload`、`download`。", ].join("\n"), {}); assertCondition( summaryBullets.some((line) => line.includes("hwlab-gateway-tran.mjs") && line.includes("upload")), "semantic changelog should also extract Chinese summary sections", summaryBullets, ); const rolloutBody = rolloutRecordBody({ pr: { number: 506, title: "fix: preserve inner trace scroll", url: "https://github.com/pikasTech/HWLAB/pull/506" }, prData: { json: { title: "fix: preserve inner trace scroll", body: prBody, url: "https://github.com/pikasTech/HWLAB/pull/506" } }, fileSummary: { summary: { files: 2, additions: 268, deletions: 22, commits: 1 }, keyFiles: [ "modified web/hwlab-cloud-web/app.mjs", "modified web/hwlab-cloud-web/scripts/trace-scroll.test.mjs", ], }, sourceCommit: "1a3fa9e6fbc987142463ecff2cbcef240a6278f2", pipelineRun: "hwlab-g14-ci-poll-1a3fa9e6fbc9", gitopsRevision: "21462b78ce4e7dba4ea374398f60db690e290147", mergedAt: "2026-05-27T06:52:22Z", pipelineSucceededAt: "2026-05-27T06:55:38Z", finishedAt: "2026-05-27T06:55:47Z", rollout: { pipelineText: "True\nSucceeded", argoText: "21462\nSynced\nHealthy\nSucceeded\n21462", healthOk: true }, ciMetrics: parsePipelineTaskRunMetrics("hwlab-g14-ci-poll-test", [ "taskrun-a\tbuild-hwlab-cloud-api\t2026-05-27T06:54:43Z\t2026-05-27T06:54:52Z\tservice-id=hwlab-cloud-api;status=reused;build-backend=reused-catalog;", "taskrun-b\tbuild-hwlab-cloud-web\t2026-05-27T06:54:43Z\t2026-05-27T06:55:08Z\tservice-id=hwlab-cloud-web;status=published;build-backend=buildkit;", ].join("\n")), }); assertCondition( rolloutBody.includes("- 上线 changelog(语义化):"), "rollout record must label natural-language changelog separately", ); assertCondition( rolloutBody.includes("- 自动 diff 摘要:"), "rollout record must keep automatic diff summary separately", ); assertCondition( rolloutBody.indexOf("- 上线 changelog(语义化):") < rolloutBody.indexOf("- 自动 diff 摘要:"), "semantic changelog should appear before automatic diff summary", ); assertCondition( rolloutBody.includes("changed files: 2; +268 / -22; commits: 1"), "automatic diff summary should preserve file/stat evidence", ); assertCondition( rolloutBody.includes("lazy build reused: 1/2"), "rollout record should include lazy-build reused ratio", rolloutBody, ); assertCondition( rolloutBody.includes("reused services: hwlab-cloud-api") && rolloutBody.includes("rebuild services: hwlab-cloud-web"), "rollout record should list reused and rebuild services", rolloutBody, ); assertCondition( rolloutBody.includes("hwlab-cloud-api: reused, 9s") && rolloutBody.includes("hwlab-cloud-web: published, 25s"), "rollout record should include each service duration", rolloutBody, ); console.log(JSON.stringify({ ok: true, checks: [ "long-running monitor owns latest-monitor-job.json", "once jobs do not overwrite long-running monitor state", "dry-run jobs do not overwrite live monitor state", "once dry-run jobs have a distinct diagnostic state file", "git mirror sync is a manual devops-infra Job, not a CronJob", "git mirror flush is a manual devops-infra Job, not a CronJob", "trigger-current can decide whether v0.2 git mirror pre-sync is required", "git mirror status exposes source and gitops GitHub sync state plus controlled flush command", "v0.2 control-plane status help exposes targeted PipelineRun/source-commit inspection", "v0.2 control-plane render uses a detached temp worktree from a CI/CD dedicated bare repo", "v0.2 status alignment reports stale-success without coupling CI to dirty workspace state", "v0.2 PipelineRun service matrix excludes hwlab-cli", "v0.2 false-green guard checks build TaskRuns, runtime artifact source commits, and reuse provenance", "v0.2 status warns on slow build TaskRuns", "rollout brief includes natural-language changelog before automatic diff summary", "semantic changelog extracts Chinese summary sections", "rollout brief includes lazy-build reused/rebuild metrics and service durations", ], }, null, 2));