feat: retire legacy HWLAB G14 runtime

This commit is contained in:
Codex
2026-06-08 17:57:41 +00:00
parent 799f35de06
commit 0920d39c7c
6 changed files with 487 additions and 80 deletions
+405 -14
View File
@@ -3,7 +3,7 @@ import { dirname, join } from "node:path";
import { createHash, randomBytes } from "node:crypto";
import { repoRoot, rootPath, type Config } from "./config";
import { runCommand } from "./command";
import { readJob, startJob } from "./jobs";
import { cancelJob, listJobs, readJob, startJob } from "./jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "./hwlab-node-lanes";
const HWLAB_REPO = "pikasTech/HWLAB";
@@ -15,9 +15,11 @@ const V02_SOURCE_BRANCH = V02_LANE_SPEC.sourceBranch;
const V02_WORKSPACE = V02_LANE_SPEC.workspace;
const V02_CICD_REPO = V02_LANE_SPEC.cicdRepo;
const DEV_NAMESPACE = "hwlab-dev";
const PROD_NAMESPACE = "hwlab-prod";
const CI_NAMESPACE = "hwlab-ci";
const ARGO_NAMESPACE = "argocd";
const DEV_APP = "hwlab-g14-dev";
const PROD_APP = "hwlab-g14-prod";
const V02_APP = V02_LANE_SPEC.app;
const V02_PIPELINE = V02_LANE_SPEC.pipeline;
const V02_POLLER = "hwlab-v02-branch-poller";
@@ -136,6 +138,15 @@ interface G14ControlPlaneOptions {
history: boolean;
}
interface G14LegacyRetirementOptions {
action: "status" | "plan" | "execute";
dryRun: boolean;
confirm: boolean;
wait: boolean;
timeoutSeconds: number;
reason: string;
}
type V02StatusTargetMode = "latest-source-head" | "source-commit" | "pipeline-run";
interface V02ControlPlaneStatusTarget {
@@ -414,6 +425,28 @@ function parseControlPlaneOptions(args: string[]): G14ControlPlaneOptions {
};
}
function parseLegacyRetirementOptions(args: string[]): G14LegacyRetirementOptions {
const [actionRaw = "status"] = args;
if (actionRaw !== "status" && actionRaw !== "plan" && actionRaw !== "execute") {
throw new Error("retirement usage: status|plan|execute [--dry-run|--confirm] [--wait] [--reason text] [--timeout-seconds N]");
}
const confirm = args.includes("--confirm");
const explicitDryRun = args.includes("--dry-run");
if (confirm && explicitDryRun) throw new Error("retirement accepts only one of --confirm or --dry-run");
if (confirm && actionRaw !== "execute") throw new Error("--confirm is only valid for retirement execute");
if (args.includes("--wait") && actionRaw !== "execute") throw new Error("--wait is only valid for retirement execute");
const reason = optionValue(args, "--reason") ?? "user requested immediate retirement of legacy G14 DEV/PROD";
if (reason.length > 240) throw new Error("--reason is limited to 240 characters");
return {
action: actionRaw,
confirm,
wait: args.includes("--wait"),
dryRun: actionRaw !== "execute" || explicitDryRun || !confirm,
timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", actionRaw === "execute" ? 60 : 30, 300),
reason,
};
}
function parseToolsImageOptions(args: string[]): G14ToolsImageOptions {
const [actionRaw] = args;
if (actionRaw !== "status" && actionRaw !== "build") {
@@ -5045,6 +5078,327 @@ function runG14Secret(options: G14SecretOptions): Record<string, unknown> {
};
}
function legacyG14RetirementStatePath(): string {
return rootPath(".state", "hwlab-g14", "legacy-g14-retirement.json");
}
function legacyG14RetirementApplications(): string[] {
return [DEV_APP, PROD_APP];
}
function legacyG14RetirementNamespaces(): string[] {
return [DEV_NAMESPACE, PROD_NAMESPACE];
}
function protectedG14RuntimeApplications(): string[] {
return [V02_APP, hwlabRuntimeLaneSpec("v03").app];
}
function protectedG14RuntimeNamespaces(): string[] {
return [V02_RUNTIME_NAMESPACE, hwlabRuntimeLaneSpec("v03").runtimeNamespace];
}
function readLegacyG14RetirementMarker(): Record<string, unknown> | null {
const path = legacyG14RetirementStatePath();
if (!existsSync(path)) return null;
try {
return record(JSON.parse(readFileSync(path, "utf8")) as unknown);
} catch (error) {
return { status: "unreadable", path, error: error instanceof Error ? error.message : String(error) };
}
}
function writeLegacyG14RetirementMarker(status: string, reason: string, details: Record<string, unknown> = {}): Record<string, unknown> {
const path = legacyG14RetirementStatePath();
mkdirSync(dirname(path), { recursive: true });
const previous = readLegacyG14RetirementMarker();
const marker = {
status,
reason,
requestedAt: typeof previous?.requestedAt === "string" ? previous.requestedAt : new Date().toISOString(),
updatedAt: new Date().toISOString(),
legacyApplications: legacyG14RetirementApplications(),
legacyNamespaces: legacyG14RetirementNamespaces(),
protectedApplications: protectedG14RuntimeApplications(),
protectedNamespaces: protectedG14RuntimeNamespaces(),
...details,
};
writeFileSync(path, `${JSON.stringify(marker, null, 2)}\n`, "utf8");
return { ...marker, path };
}
function legacyG14RetirementBlocksMonitor(): Record<string, unknown> | null {
const marker = readLegacyG14RetirementMarker();
if (marker === null) {
return {
status: "retired-by-contract",
path: legacyG14RetirementStatePath(),
reason: "legacy G14 DEV/PROD monitor is retired; inspect retirement status for live cluster evidence",
next: { status: "bun scripts/cli.ts hwlab g14 retirement status", v02Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02" },
};
}
const status = String(marker.status ?? "");
return status === "retiring" || status === "retired" || status === "failed" || status === "unreadable" ? marker : null;
}
function k8sMetadataNamespace(item: Record<string, unknown>): string | null {
const namespace = record(item.metadata).namespace;
return typeof namespace === "string" && namespace.length > 0 ? namespace : null;
}
function applicationSummary(item: Record<string, unknown>): Record<string, unknown> {
return {
name: k8sMetadataName(item),
namespace: k8sMetadataNamespace(item) ?? ARGO_NAMESPACE,
deletionTimestamp: record(item.metadata).deletionTimestamp ?? null,
sync: nested(item, ["status", "sync", "status"]) ?? null,
health: nested(item, ["status", "health", "status"]) ?? null,
revision: nested(item, ["status", "sync", "revision"]) ?? null,
};
}
function namespaceSummary(item: Record<string, unknown>): Record<string, unknown> {
return {
name: k8sMetadataName(item),
phase: nested(item, ["status", "phase"]) ?? null,
deletionTimestamp: record(item.metadata).deletionTimestamp ?? null,
};
}
function parseNamespacedResourcePreview(text: string): Record<string, unknown>[] {
const groups: Record<string, string[]> = {};
let current = "";
for (const line of text.split(/\r?\n/u)) {
const trimmed = line.trim();
if (trimmed.length === 0) continue;
const namespaceMatch = /^__namespace\t(.+)$/u.exec(trimmed);
if (namespaceMatch !== null) {
current = namespaceMatch[1] ?? "";
if (current.length > 0) groups[current] = [];
continue;
}
if (current.length > 0) groups[current]?.push(trimmed);
}
return Object.entries(groups).map(([namespace, items]) => ({
namespace,
count: items.length,
preview: items.slice(0, 40),
truncated: items.length > 40,
}));
}
function legacyG14RetirementStatusScript(): string {
const legacyApps = legacyG14RetirementApplications().map(shellQuote).join(" ");
const legacyNamespaces = legacyG14RetirementNamespaces().map(shellQuote).join(" ");
const protectedApps = protectedG14RuntimeApplications().map(shellQuote).join(" ");
const protectedNamespaces = protectedG14RuntimeNamespaces().map(shellQuote).join(" ");
return [
"set +e",
"section() {",
" name=\"$1\"",
" shift",
" printf '__UNIDESK_SECTION_BEGIN__ %s\\n' \"$name\"",
" \"$@\"",
" code=$?",
" printf '\\n__UNIDESK_SECTION_END__ %s exit=%s\\n' \"$name\" \"$code\"",
"}",
`section legacyApplications kubectl get application -n ${shellQuote(ARGO_NAMESPACE)} ${legacyApps} --ignore-not-found=true -o json`,
`section legacyNamespaces kubectl get namespace ${legacyNamespaces} --ignore-not-found=true -o json`,
`section protectedApplications kubectl get application -n ${shellQuote(ARGO_NAMESPACE)} ${protectedApps} --ignore-not-found=true -o json`,
`section protectedNamespaces kubectl get namespace ${protectedNamespaces} --ignore-not-found=true -o json`,
"section legacyResources sh -lc " + shellQuote([
`for ns in ${legacyNamespaces}; do`,
" printf '__namespace\\t%s\\n' \"$ns\"",
" kubectl get deploy,statefulset,svc,pod,pvc,ingress,configmap,secret -n \"$ns\" -o name --ignore-not-found=true 2>/dev/null | sed -n '1,160p'",
"done",
].join("\n")),
].join("\n");
}
function legacyG14RetirementStatus(): Record<string, unknown> {
const result = g14K3s(["script", "--", legacyG14RetirementStatusScript()], 60_000);
const sections = parseShellSections(statusText(result));
const legacyApplications = parseSectionJsonArray(sections.legacyApplications).map(applicationSummary);
const legacyNamespaces = parseSectionJsonArray(sections.legacyNamespaces).map(namespaceSummary);
const protectedApplications = parseSectionJsonArray(sections.protectedApplications).map(applicationSummary);
const protectedNamespaces = parseSectionJsonArray(sections.protectedNamespaces).map(namespaceSummary);
const terminating = legacyApplications.some((item) => item.deletionTimestamp !== null)
|| legacyNamespaces.some((item) => item.deletionTimestamp !== null || item.phase === "Terminating");
const retired = legacyApplications.length === 0 && legacyNamespaces.length === 0;
const retirementState = retired ? "retired" : terminating ? "terminating" : "active";
const protectedAppNames = new Set(protectedApplications.map((item) => item.name).filter((name): name is string => typeof name === "string"));
const protectedNamespaceNames = new Set(protectedNamespaces.map((item) => item.name).filter((name): name is string => typeof name === "string"));
const missingProtectedApplications = protectedG14RuntimeApplications().filter((name) => !protectedAppNames.has(name));
const missingProtectedNamespaces = protectedG14RuntimeNamespaces().filter((name) => !protectedNamespaceNames.has(name));
const sectionsOk =
shellSectionOk(sections.legacyApplications) &&
shellSectionOk(sections.legacyNamespaces) &&
shellSectionOk(sections.protectedApplications) &&
shellSectionOk(sections.protectedNamespaces) &&
shellSectionOk(sections.legacyResources);
return {
ok: isCommandSuccess(result) && sectionsOk,
command: "hwlab g14 retirement status",
provider: G14_PROVIDER,
argoNamespace: ARGO_NAMESPACE,
retirementState,
retired,
legacy: {
applications: legacyApplications,
namespaces: legacyNamespaces,
resources: parseNamespacedResourcePreview(String(sections.legacyResources?.stdout ?? "")),
},
protected: {
applications: protectedApplications,
namespaces: protectedNamespaces,
missingApplications: missingProtectedApplications,
missingNamespaces: missingProtectedNamespaces,
ok: missingProtectedApplications.length === 0 && missingProtectedNamespaces.length === 0,
},
marker: readLegacyG14RetirementMarker(),
result: compactCommandResult(result),
next: retired
? { verifyV02: "bun scripts/cli.ts hwlab g14 control-plane status --lane v02", verifyV03: "bun scripts/cli.ts hwlab nodes control-plane status --node G14 --lane v03" }
: { plan: "bun scripts/cli.ts hwlab g14 retirement plan", execute: "bun scripts/cli.ts hwlab g14 retirement execute --confirm" },
};
}
function legacyG14RetirementPlan(status = legacyG14RetirementStatus()): Record<string, unknown> {
return {
ok: status.ok === true,
command: "hwlab g14 retirement plan",
mode: "dry-run",
mutation: false,
reason: "retire legacy G14 DEV/PROD runtime and support surface without touching v0.2/v0.3",
destructiveTargets: {
applications: legacyG14RetirementApplications().map((name) => ({ apiVersion: "argoproj.io/v1alpha1", kind: "Application", namespace: ARGO_NAMESPACE, name })),
namespaces: legacyG14RetirementNamespaces().map((name) => ({ apiVersion: "v1", kind: "Namespace", name })),
},
protectedTargets: {
applications: protectedG14RuntimeApplications().map((name) => ({ apiVersion: "argoproj.io/v1alpha1", kind: "Application", namespace: ARGO_NAMESPACE, name })),
namespaces: protectedG14RuntimeNamespaces().map((name) => ({ apiVersion: "v1", kind: "Namespace", name })),
},
localSupportActions: [
{ action: "write-retirement-marker", path: legacyG14RetirementStatePath() },
{ action: "cancel-active-local-job", jobName: "hwlab_g14_pr_monitor" },
{ action: "block-new-monitor-starts", command: "bun scripts/cli.ts hwlab g14 monitor-prs --lane g14" },
],
status,
next: { execute: "bun scripts/cli.ts hwlab g14 retirement execute --confirm" },
};
}
function legacyG14RecordRolloutRetired(): Record<string, unknown> {
return {
ok: false,
command: "hwlab g14 record-rollout",
phase: "retired",
degradedReason: "legacy-g14-dev-prod-retired",
message: "Legacy G14 DEV rollout recording is retired. Use active runtime lane closeout/status evidence instead.",
retirement: readLegacyG14RetirementMarker(),
next: {
retirementStatus: "bun scripts/cli.ts hwlab g14 retirement status",
v02Status: "bun scripts/cli.ts hwlab g14 control-plane status --lane v02",
v03Status: "bun scripts/cli.ts hwlab nodes control-plane status --node G14 --lane v03",
},
};
}
function cancelLegacyG14MonitorJobs(dryRun: boolean): Record<string, unknown> {
const candidates = listJobs()
.filter((job) => job.name === "hwlab_g14_pr_monitor" && (job.status === "queued" || job.status === "running"))
.map((job) => ({ id: job.id, name: job.name, status: job.status, createdAt: job.createdAt, runnerPid: job.runnerPid ?? null }));
if (dryRun) return { ok: true, dryRun: true, candidates, actions: candidates.map((job) => ({ action: "would-cancel", ...job })) };
const actions = candidates.map((job) => cancelJob(job.id));
return { ok: actions.every((action) => record(action).ok !== false), dryRun: false, candidates, actions };
}
function legacyG14RetirementDeleteScript(): string {
const legacyApps = legacyG14RetirementApplications();
const legacyNamespaces = legacyG14RetirementNamespaces();
return [
"set +e",
"overall=0",
"printf '__UNIDESK_SECTION_BEGIN__ deleteApplications\\n'",
...legacyApps.map((app) => [
`kubectl delete application -n ${shellQuote(ARGO_NAMESPACE)} ${shellQuote(app)} --ignore-not-found=true --wait=false`,
"code=$?",
"[ \"$code\" -eq 0 ] || overall=1",
`printf 'application\\t%s\\texit=%s\\n' ${shellQuote(app)} "$code"`,
].join("\n")),
"printf '__UNIDESK_SECTION_END__ deleteApplications exit=0\\n'",
"printf '__UNIDESK_SECTION_BEGIN__ deleteNamespaces\\n'",
...legacyNamespaces.map((namespace) => [
`kubectl delete namespace ${shellQuote(namespace)} --ignore-not-found=true --wait=false`,
"code=$?",
"[ \"$code\" -eq 0 ] || overall=1",
`printf 'namespace\\t%s\\texit=%s\\n' ${shellQuote(namespace)} "$code"`,
].join("\n")),
"printf '__UNIDESK_SECTION_END__ deleteNamespaces exit=0\\n'",
"exit \"$overall\"",
].join("\n");
}
function waitForLegacyG14Retirement(timeoutSeconds: number): Record<string, unknown> {
const startedAtMs = Date.now();
let lastStatus: Record<string, unknown> = {};
let attempts = 0;
while (Date.now() - startedAtMs <= timeoutSeconds * 1000) {
attempts += 1;
lastStatus = legacyG14RetirementStatus();
if (lastStatus.retired === true) {
return { ok: true, attempts, elapsedMs: Date.now() - startedAtMs, status: lastStatus };
}
const wait = runCommand(["sleep", "5"], repoRoot, { timeoutMs: 7_000 });
if (wait.exitCode !== 0) break;
}
return { ok: false, attempts, elapsedMs: Date.now() - startedAtMs, status: lastStatus, degradedReason: "legacy-g14-retirement-not-complete-before-timeout" };
}
function runLegacyG14Retirement(options: G14LegacyRetirementOptions): Record<string, unknown> {
const before = legacyG14RetirementStatus();
if (options.action === "status") return before;
if (options.action === "plan" || options.dryRun) return { ...legacyG14RetirementPlan(before), command: `hwlab g14 retirement ${options.action}`, reason: options.reason };
const markerBeforeDelete = writeLegacyG14RetirementMarker("retiring", options.reason, { command: "hwlab g14 retirement execute --confirm" });
const localJobs = cancelLegacyG14MonitorJobs(false);
const deleteResult = g14K3s(["script", "--", legacyG14RetirementDeleteScript()], Math.max(60_000, options.timeoutSeconds * 1000));
const deleteSections = parseShellSections(statusText(deleteResult));
const afterDelete = legacyG14RetirementStatus();
const wait = options.wait ? waitForLegacyG14Retirement(options.timeoutSeconds) : null;
const finalStatus = wait === null ? afterDelete : record(wait.status);
const liveMutationOk = isCommandSuccess(deleteResult);
const finalRetired = finalStatus.retired === true;
const marker = writeLegacyG14RetirementMarker(finalRetired ? "retired" : liveMutationOk ? "retiring" : "failed", options.reason, {
command: "hwlab g14 retirement execute --confirm",
deleteExitCode: deleteResult.exitCode,
finalRetirementState: finalStatus.retirementState ?? null,
});
return {
ok: liveMutationOk && (options.wait ? finalRetired : finalStatus.retirementState !== "active"),
command: "hwlab g14 retirement execute",
mode: "confirmed-retirement",
mutation: true,
reason: options.reason,
before,
markerBeforeDelete,
localJobs,
delete: {
ok: liveMutationOk,
applications: String(deleteSections.deleteApplications?.stdout ?? "").split(/\r?\n/u).filter(Boolean),
namespaces: String(deleteSections.deleteNamespaces?.stdout ?? "").split(/\r?\n/u).filter(Boolean),
result: compactCommandResult(deleteResult),
},
afterDelete,
wait,
finalStatus,
marker,
next: finalRetired
? { verifyV02: "bun scripts/cli.ts hwlab g14 control-plane status --lane v02", verifyV03: "bun scripts/cli.ts hwlab nodes control-plane status --node G14 --lane v03" }
: { status: "bun scripts/cli.ts hwlab g14 retirement status" },
};
}
function deleteLegacyGitMirrorCronJob(dryRun: boolean): CommandJsonResult {
return g14K3s([
"kubectl",
@@ -7991,14 +8345,14 @@ function newDailyBriefBody(date: string): string {
"",
"## 常驻观察与长期建议",
"",
"- G14 HWLAB source truth 固定为 G14 `/root/hwlab` 与 `origin/G14`DEV rollout 只接受 G14 Tekton/GitOps/Argo 和公网 health 证据。",
"- Legacy G14 DEV rollout 已退役;当前 HWLAB runtime 证据必须来自 active runtime lane。",
"- GitHub issue/PR 写操作必须走 UniDesk `gh` 子命令;G14 k3s 操作必须走 UniDesk `trans G14:k3s`。",
].join("\n");
}
function insertBriefIndexRow(indexBody: string, date: string, brief: { number: number; url: string }): string {
if (parseBriefIssueFromIndex(indexBody, date) !== null) return indexBody;
const row = `| ${date} | [#${brief.number}](${brief.url}) | 已创建当日 G14 DEV rollout 自动简报入口;后续每次 DEV 上线记录 CI/CD 耗时和上线 changelog。 | 继续监控 HWLAB base=G14 PRready 后自动合并并滚动到 G14 DEV。 |`;
const row = `| ${date} | [#${brief.number}](${brief.url}) | legacy G14 DEV rollout 自动简报入口已退役;当前证据写入 active runtime lane closeout。 | 使用 active runtime lane 的受控 CI/CD 入口和 issue 评论记录。 |`;
const marker = "### 指挥简报索引";
const markerIndex = indexBody.indexOf(marker);
if (markerIndex === -1) return `${indexBody.trimEnd()}\n\n${marker}\n\n| 日期(北京时间) | 指挥简报 issue | 当日推进 | 下一步计划 |\n| --- | --- | --- | --- |\n${row}\n`;
@@ -8131,7 +8485,7 @@ export function rolloutRecordBody(input: {
"",
`## 更新 ${now.date} ${now.time.slice(0, 5)} 北京时间`,
"",
`### G14 DEV rolloutPR #${input.pr.number}`,
`### Retired legacy G14 DEV rollout recordPR #${input.pr.number}`,
"",
`- PR: [#${input.pr.number} ${title}](${url})`,
`- 合并 commit: \`${input.sourceCommit}\``,
@@ -8827,6 +9181,17 @@ async function monitorV02Cycle(options: G14MonitorOptions, cycle: number): Promi
async function monitorCycle(options: G14MonitorOptions, cycle: number): Promise<Record<string, unknown>> {
if (options.lane === "v02") return monitorV02Cycle(options, cycle);
const retirement = legacyG14RetirementBlocksMonitor();
if (retirement !== null) {
return {
ok: false,
cycle,
phase: "retired",
degradedReason: "legacy-g14-dev-prod-retired",
retirement,
next: { status: "bun scripts/cli.ts hwlab g14 retirement status", v02Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02" },
};
}
printEvent("g14.monitor.cycle.start", { cycle, dryRun: options.dryRun });
const precheck = precheckWorkspace();
if (!isCommandSuccess(precheck)) return { ok: false, cycle, phase: "workspace-precheck", precheck };
@@ -8881,12 +9246,12 @@ export function hwlabG14Help(): Record<string, unknown> {
command: "hwlab g14",
output: "json",
usage: [
"bun scripts/cli.ts hwlab g14 monitor-prs",
"bun scripts/cli.ts hwlab g14 monitor-prs --lane v02",
"bun scripts/cli.ts hwlab g14 monitor-prs --lane v02 --status",
"bun scripts/cli.ts hwlab g14 monitor-prs --once --dry-run",
"bun scripts/cli.ts hwlab g14 retirement status",
"bun scripts/cli.ts hwlab g14 retirement plan",
"bun scripts/cli.ts hwlab g14 retirement execute --confirm",
"bun scripts/cli.ts hwlab g14 monitor-prs --lane v02 --once --dry-run",
"bun scripts/cli.ts hwlab g14 record-rollout --pr <number> [--source-commit sha]",
"bun scripts/cli.ts hwlab g14 control-plane status --lane v02",
"bun scripts/cli.ts hwlab g14 control-plane status --lane v02 --history",
"bun scripts/cli.ts hwlab g14 control-plane status --lane v02 --pipeline-run hwlab-v02-ci-poll-<short-sha>",
@@ -8955,20 +9320,22 @@ export function hwlabG14Help(): Record<string, unknown> {
"bun scripts/cli.ts hwlab g14 upstream-image ensure --name openfga --tag v1.17.0 --confirm",
"bun scripts/cli.ts job status <jobId> --tail-bytes 30000",
],
description: "G14 HWLAB PR monitor, DEV rollout command, bounded v0.2 control-plane bootstrap/cleanup/runtime-migration helper, node-scoped runtime lane v03 control-plane apply/status/refresh/trigger entry, runtime lane SecretRef bootstrap, devops-infra git mirror and observability maintenance, controlled CI tools image build/status entry, and allowlisted upstream image mirroring. The public monitor starts a fire-and-forget job. Default monitor lane is base=G14; --lane v02 monitors base=v0.2 PRs, waits for GitHub preflight/CI readiness, automatically merges ready PRs without waiting for other active v0.2 PipelineRuns, triggers v0.2 CD with latest-only GitOps writeback, flushes the git mirror when needed, and posts deduplicated PR comments for pending, blocked/conflict, success, superseded, failure, or timeout states. confirmed control-plane trigger-current and git-mirror sync/flush also return async jobs by default, with --wait reserved for explicit synchronous debugging. control-plane v02 keeps the full closeout/cleanup/runtime-migration verdict path; v03+ is advertised through `hwlab nodes ... --node <node-id> --lane vNN` so node identity remains configuration data instead of a command family. secret status/ensure is the standard runtime lane SecretRef bootstrap path for OpenFGA and master admin API key; it never reads or prints secret values. upstream-image status/ensure only mirrors allowlisted upstream runtime images into the G14 local registry. git-mirror status/apply/sync/flush is the manual devops-infra mirror/relay control path and does not install a CronJob. observability status/apply/query/targets/boundary/closeout owns the shared Prometheus Operator and Prometheus instance in devops-infra, adds bounded PromQL assertions and semantic closeout summaries, while HWLAB lane manifests own only ServiceMonitor and PrometheusRule objects.",
description: "G14 HWLAB PR monitor, legacy DEV/PROD retirement status/plan/execute command, bounded v0.2 control-plane bootstrap/cleanup/runtime-migration helper, node-scoped runtime lane v03 control-plane apply/status/refresh/trigger entry, runtime lane SecretRef bootstrap, devops-infra git mirror and observability maintenance, controlled CI tools image build/status entry, and allowlisted upstream image mirroring. The legacy base=G14 monitor is blocked by the retirement contract; the local retirement marker records live execution evidence. `--lane v02` monitors base=v0.2 PRs, waits for GitHub preflight/CI readiness, automatically merges ready PRs without waiting for other active v0.2 PipelineRuns, triggers v0.2 CD with latest-only GitOps writeback, flushes the git mirror when needed, and posts deduplicated PR comments for pending, blocked/conflict, success, superseded, failure, or timeout states. confirmed control-plane trigger-current and git-mirror sync/flush also return async jobs by default, with --wait reserved for explicit synchronous debugging. control-plane v02 keeps the full closeout/cleanup/runtime-migration verdict path; v03+ is advertised through `hwlab nodes ... --node <node-id> --lane vNN` so node identity remains configuration data instead of a command family. secret status/ensure is the standard runtime lane SecretRef bootstrap path for OpenFGA and master admin API key; it never reads or prints secret values. upstream-image status/ensure only mirrors allowlisted upstream runtime images into the G14 local registry. git-mirror status/apply/sync/flush is the manual devops-infra mirror/relay control path and does not install a CronJob. observability status/apply/query/targets/boundary/closeout owns the shared Prometheus Operator and Prometheus instance in devops-infra, adds bounded PromQL assertions and semantic closeout summaries, while HWLAB lane manifests own only ServiceMonitor and PrometheusRule objects.",
defaults: {
repo: HWLAB_REPO,
base: G14_SOURCE_BRANCH,
legacyBase: G14_SOURCE_BRANCH,
v02Base: V02_SOURCE_BRANCH,
runtimeLaneConfig: hwlabRuntimeLaneConfigPath(),
runtimeLanes: hwlabRuntimeLaneIds(),
provider: G14_PROVIDER,
workspace: G14_WORKSPACE,
legacyWorkspace: G14_WORKSPACE,
v02Workspace: V02_WORKSPACE,
v03Workspace: hwlabRuntimeLaneSpec("v03").workspace,
ciToolsImageRepo: G14_CI_TOOLS_IMAGE_REPO,
intervalSeconds: DEFAULT_INTERVAL_SECONDS,
devApplication: DEV_APP,
prodApplication: PROD_APP,
legacyRetirementState: legacyG14RetirementStatePath(),
v02Application: V02_APP,
v03Application: hwlabRuntimeLaneSpec("v03").app,
v03Node: hwlabRuntimeLaneSpec("v03").nodeId,
@@ -8999,6 +9366,7 @@ function monitorStatus(options: G14MonitorOptions): Record<string, unknown> {
const stateFileName = hwlabG14MonitorStateFileName(options);
const stateFileRole = hwlabG14MonitorStateRole(options);
const latestPath = join(stateDir, stateFileName);
const retirement = options.lane === "g14" ? legacyG14RetirementBlocksMonitor() : null;
const exists = existsSync(latestPath);
let latest: Record<string, unknown> | null = null;
let job: Record<string, unknown> | null = null;
@@ -9047,7 +9415,11 @@ function monitorStatus(options: G14MonitorOptions): Record<string, unknown> {
job,
statusCommand,
degradedReason,
next: statusCommand === null ? {
retirement,
next: retirement !== null ? {
retirementStatus: "bun scripts/cli.ts hwlab g14 retirement status",
v02Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02",
} : statusCommand === null ? {
start: `bun scripts/cli.ts hwlab g14 monitor-prs --lane ${options.lane}`,
} : {
status: statusCommand,
@@ -9059,8 +9431,7 @@ export async function runHwlabG14Command(_config: Config, args: string[]): Promi
if (args.length === 0 || args.includes("--help") || args.includes("-h")) return { ok: true, ...hwlabG14Help() };
const [action] = args;
if (action === "record-rollout") {
const options = parseRecordRolloutOptions(args.slice(1));
return appendRolloutBrief(options);
return legacyG14RecordRolloutRetired();
}
if (action === "control-plane") {
const options = parseControlPlaneOptions(args.slice(1));
@@ -9072,6 +9443,10 @@ export async function runHwlabG14Command(_config: Config, args: string[]): Promi
}
return runV02ControlPlane(options);
}
if (action === "retirement") {
const options = parseLegacyRetirementOptions(args.slice(1));
return runLegacyG14Retirement(options);
}
if (action === "secret") {
const options = parseSecretOptions(args.slice(1));
return runG14Secret(options);
@@ -9096,11 +9471,27 @@ export async function runHwlabG14Command(_config: Config, args: string[]): Promi
return runG14Observability(options);
}
if (action !== "monitor-prs") {
return { ok: false, command: `hwlab g14 ${action ?? ""}`.trim(), degradedReason: "unsupported-command", message: "supported commands: hwlab g14 monitor-prs, hwlab g14 record-rollout, hwlab g14 control-plane, hwlab g14 secret, hwlab g14 git-mirror, hwlab g14 observability, hwlab g14 tools-image, hwlab g14 upstream-image" };
return { ok: false, command: `hwlab g14 ${action ?? ""}`.trim(), degradedReason: "unsupported-command", message: "supported commands: hwlab g14 monitor-prs, hwlab g14 retirement, hwlab g14 control-plane, hwlab g14 secret, hwlab g14 git-mirror, hwlab g14 observability, hwlab g14 tools-image, hwlab g14 upstream-image" };
}
const options = parseOptions(args.slice(1));
if (options.worker) return runMonitorWorker(options);
if (args.includes("--status")) return monitorStatus(options);
const retirement = options.lane === "g14" ? legacyG14RetirementBlocksMonitor() : null;
if (retirement !== null) {
return {
ok: false,
command: "hwlab g14 monitor-prs",
lane: options.lane,
baseBranch: monitorBaseBranch(options.lane),
degradedReason: "legacy-g14-dev-prod-retired",
message: "Legacy G14 DEV/PROD has a retirement marker; start v0.2 monitor with --lane v02 or inspect retirement status.",
retirement,
next: {
retirementStatus: "bun scripts/cli.ts hwlab g14 retirement status",
v02Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02",
},
};
}
const command = ["bun", "scripts/cli.ts", "hwlab", "g14", "monitor-prs", "--worker", "--lane", options.lane, "--interval-seconds", String(options.intervalSeconds), "--timeout-seconds", String(options.timeoutSeconds), ...(options.once ? ["--once"] : []), ...(options.dryRun ? ["--dry-run"] : []), ...(options.maxCycles > 0 ? ["--max-cycles", String(options.maxCycles)] : [])];
const jobName = options.lane === "v02" ? "hwlab_g14_v02_pr_monitor" : "hwlab_g14_pr_monitor";
const jobNote = options.lane === "v02"