refactor: variableize yaml lane node naming

This commit is contained in:
Codex
2026-07-09 10:46:55 +02:00
parent e9457ace4a
commit 37a6b70793
123 changed files with 1572 additions and 1571 deletions
+2 -2
View File
@@ -392,8 +392,8 @@ async function main(): Promise<void> {
return;
}
if (sub === "g14") {
const { runHwlabG14Command } = await import("./src/hwlab-g14");
const result = await runHwlabG14Command(readConfig(), args.slice(2));
const { runHwlabLegacyRuntimeCommand } = await import("./src/hwlab-legacy-runtime");
const result = await runHwlabLegacyRuntimeCommand(readConfig(), args.slice(2));
const ok = (result as { ok?: unknown }).ok !== false;
emitJson(commandName, result, ok);
if (!ok) process.exitCode = 1;
@@ -20,11 +20,11 @@ import {
type DeployJsonExecutorMirror,
type DeployJsonServiceContract,
} from "../deploy-json-contract";
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
import { k3sGuardShellLines } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import type { ArtifactConsumerSpec, ArtifactConsumerTarget, ArtifactRegistryOptions } from "./types";
import { base64, d601K3sGuardScript, shellQuote } from "./bundle";
import { base64, k3sTargetGuardScript, shellQuote } from "./bundle";
import { artifactImageRef, deployRefFor, sourceRepoFor } from "./consumer";
export function registryArtifactProbeScript(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, commit: string): string {
@@ -76,7 +76,7 @@ export function verifyLocalArtifactLabels(
};
}
export function d601DevFrontendAuthPatchScript(config: UniDeskConfig): string {
export function devFrontendAuthPatchScript(config: UniDeskConfig): string {
const sshClientToken = composeRuntimeEnvValue("UNIDESK_SSH_CLIENT_TOKEN");
if (sshClientToken === null) throw new Error("UNIDESK_SSH_CLIENT_TOKEN must be present in .state/docker-compose.env before deploying dev frontend");
const sshClientRouteAllowlist = composeRuntimeEnvValue("UNIDESK_SSH_CLIENT_ROUTE_ALLOWLIST") ?? "G14,G14:*,D601,D601:*";
@@ -91,7 +91,7 @@ export function d601DevFrontendAuthPatchScript(config: UniDeskConfig): string {
UNIDESK_SSH_CLIENT_ROUTE_ALLOWLIST: sshClientRouteAllowlist,
};
return [
d601K3sGuardScript(),
k3sTargetGuardScript(),
`secret_patch=${shellQuote(JSON.stringify({ data: secretData }))}`,
`config_patch=${shellQuote(JSON.stringify({ data: configData }))}`,
"kubectl -n unidesk-dev patch secret unidesk-dev-runtime-secrets --type merge -p \"$secret_patch\"",
+3 -3
View File
@@ -20,7 +20,7 @@ import {
type DeployJsonExecutorMirror,
type DeployJsonServiceContract,
} from "../deploy-json-contract";
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
import { k3sGuardShellLines } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import type { ArtifactRegistryOptions, RenderedBundle, RenderedFile } from "./types";
@@ -33,8 +33,8 @@ export function shellQuote(value: string): string {
return `'${value.replace(/'/g, `'\\''`)}'`;
}
export function d601K3sGuardScript(): string {
return d601K3sGuardShellLines().join("\n");
export function k3sTargetGuardScript(): string {
return k3sGuardShellLines().join("\n");
}
export function base64(value: string): string {
+1 -1
View File
@@ -20,7 +20,7 @@ import {
type DeployJsonExecutorMirror,
type DeployJsonServiceContract,
} from "../deploy-json-contract";
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
import { k3sGuardShellLines } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import type { ArtifactConsumerSpec, AuthHealthGate, RuntimeSecretRequirement } from "./types";
@@ -21,7 +21,7 @@ import {
type DeployJsonExecutorMirror,
type DeployJsonServiceContract,
} from "../deploy-json-contract";
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
import { k3sGuardShellLines } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import { artifactRegistryOptionsTargetSummary, type ArtifactConsumerSpec, type ArtifactConsumerTarget, type ArtifactRegistryOptions } from "./types";
@@ -33,7 +33,7 @@ import { composeLockScript } from "./install";
import { rollbackInfo } from "./k3s-deploy";
import { runReadonlyStatus } from "./readonly";
import { runRemoteScript } from "./remote";
import { authHealthGateScript, composeArtifactRuntime, composeArtifactSecretPreflight, pullArtifactFromD601, runtimeSecretContractForComposeTarget, runtimeSecretRecommendedActionForService, upsertEnvFileValues } from "./runtime-secrets";
import { authHealthGateScript, composeArtifactRuntime, composeArtifactSecretPreflight, pullArtifactFromRegistryTarget, runtimeSecretContractForComposeTarget, runtimeSecretRecommendedActionForService, upsertEnvFileValues } from "./runtime-secrets";
export async function deployComposeArtifactNow(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget): Promise<Record<string, unknown>> {
const commit = options.commit;
@@ -85,7 +85,7 @@ export async function deployComposeArtifactNow(options: ArtifactRegistryOptions,
registryProbe: commandTail(registryProbe),
};
}
const pull = await pullArtifactFromD601(options, sourceImage);
const pull = await pullArtifactFromRegistryTarget(options, sourceImage);
if (pull.exitCode !== 0 || pull.timedOut) {
return {
ok: false,
@@ -211,7 +211,7 @@ export async function deployBackendCoreNow(options: ArtifactRegistryOptions): Pr
return deployComposeArtifactNow(options, spec, target);
}
export function d601ComposeArtifactDeployScript(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget, commit: string): string {
export function composeTargetArtifactDeployScript(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget, commit: string): string {
if (target.compose === undefined) throw new Error(`${spec.serviceId} missing compose artifact consumer config`);
const compose = target.compose;
const sourceImage = artifactImageRef(options, spec, commit);
@@ -313,7 +313,7 @@ export function d601ComposeArtifactDeployScript(options: ArtifactRegistryOptions
].join("\n");
}
export async function deployD601ComposeArtifactNow(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget): Promise<Record<string, unknown>> {
export async function deployComposeTargetArtifactNow(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget): Promise<Record<string, unknown>> {
const environment = options.environment ?? "prod";
const commit = options.commit;
if (commit === null) throw new Error("artifact-registry deploy-service requires --commit <full-sha>");
@@ -333,7 +333,7 @@ export async function deployD601ComposeArtifactNow(options: ArtifactRegistryOpti
registryProbe: commandTail(registryProbe),
};
}
const deploy = runRemoteScript(options, d601ComposeArtifactDeployScript(options, spec, target, commit), Math.max(options.timeoutMs, 420_000));
const deploy = runRemoteScript(options, composeTargetArtifactDeployScript(options, spec, target, commit), Math.max(options.timeoutMs, 420_000));
if (deploy.exitCode !== 0 || deploy.timedOut) {
return {
ok: false,
+1 -1
View File
@@ -20,7 +20,7 @@ import {
type DeployJsonExecutorMirror,
type DeployJsonServiceContract,
} from "../deploy-json-contract";
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
import { k3sGuardShellLines } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import type { ArtifactConsumerSpec, ArtifactConsumerTarget, ArtifactDeployEnvironment, ArtifactRegistryOptions, SupportedArtifactConsumerService } from "./types";
+5 -5
View File
@@ -21,15 +21,15 @@ import {
type DeployJsonExecutorMirror,
type DeployJsonServiceContract,
} from "../deploy-json-contract";
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
import { k3sGuardShellLines } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import type { ArtifactRegistryAction, ArtifactRegistryCommandRuntime, ArtifactRegistryOptions } from "./types";
import { renderBundle } from "./bundle";
import { deployBackendCoreJob, deployBackendCoreNow, deployComposeArtifactNow, deployD601ComposeArtifactNow, dryRunArtifactConsumerPlan, legacyDeployBackendCoreResult } from "./compose-deploy";
import { deployBackendCoreJob, deployBackendCoreNow, deployComposeArtifactNow, deployComposeTargetArtifactNow, dryRunArtifactConsumerPlan, legacyDeployBackendCoreResult } from "./compose-deploy";
import { artifactConsumerLiveBlock, artifactConsumerSpec, artifactConsumerTarget, supportedArtifactConsumers, unsupportedEnvironment, unsupportedService } from "./consumer";
import { install, installDryRun } from "./install";
import { deployD601K3sArtifactNow, rollbackInfo } from "./k3s-deploy";
import { deployK3sTargetArtifactNow, rollbackInfo } from "./k3s-deploy";
import { isHelpArg, parseArtifactRegistryOptions } from "./options";
import { runReadonlyStatusWithRemoteFallback } from "./readonly";
import { plan } from "./status";
@@ -46,8 +46,8 @@ export async function deployServiceNow(options: ArtifactRegistryOptions): Promis
const liveBlock = artifactConsumerLiveBlock(spec, options);
if (liveBlock !== null) return liveBlock;
if (spec.kind === "compose") return deployComposeArtifactNow(options, spec, target);
if (spec.kind === "d601-compose") return deployD601ComposeArtifactNow(options, spec, target);
return deployD601K3sArtifactNow(options, spec, target);
if (spec.kind === "d601-compose") return deployComposeTargetArtifactNow(options, spec, target);
return deployK3sTargetArtifactNow(options, spec, target);
}
export function deployServiceJob(args: string[], options: ArtifactRegistryOptions): Record<string, unknown> {
+1 -1
View File
@@ -20,7 +20,7 @@ import {
type DeployJsonExecutorMirror,
type DeployJsonServiceContract,
} from "../deploy-json-contract";
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
import { k3sGuardShellLines } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import type { ArtifactRegistryOptions, RenderedFile, RuntimeSecretPresence, RuntimeSecretRequirement } from "./types";
+8 -8
View File
@@ -20,12 +20,12 @@ import {
type DeployJsonExecutorMirror,
type DeployJsonServiceContract,
} from "../deploy-json-contract";
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
import { k3sGuardShellLines } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import type { ArtifactConsumerSpec, ArtifactConsumerTarget, ArtifactDeployEnvironment, ArtifactRegistryOptions } from "./types";
import { d601DevFrontendAuthPatchScript, registryArtifactMissingMessage, registryArtifactProbeScript } from "./artifact-probe";
import { d601K3sGuardScript, rootExecPrelude, shellQuote } from "./bundle";
import { devFrontendAuthPatchScript, registryArtifactMissingMessage, registryArtifactProbeScript } from "./artifact-probe";
import { k3sTargetGuardScript, rootExecPrelude, shellQuote } from "./bundle";
import { artifactImageRef, commandTail, deployRefFor, sourceRepoFor } from "./consumer";
import { runReadonlyStatus } from "./readonly";
import { runRemoteScript, runRemoteScriptBackground } from "./remote";
@@ -54,7 +54,7 @@ export function rollbackInfo(spec: ArtifactConsumerSpec, target: ArtifactConsume
};
}
export function d601K3sArtifactDeployScript(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget, commit: string): string {
export function k3sTargetArtifactDeployScript(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget, commit: string): string {
const environment = options.environment ?? "prod";
if (target.k3s === undefined) throw new Error(`${spec.serviceId} missing k3s artifact consumer config for ${environment}`);
const manifestText = readFileSync(rootPath(target.k3s.manifestRepoPath), "utf8");
@@ -120,7 +120,7 @@ export function d601K3sArtifactDeployScript(options: ArtifactRegistryOptions, sp
"command -v kubectl >/dev/null",
"command -v ctr >/dev/null",
"test -S /run/k3s/containerd/containerd.sock",
d601K3sGuardScript(),
k3sTargetGuardScript(),
`curl -fsSI -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' ${shellQuote(`http://127.0.0.1:${options.port}/v2/${spec.registryRepository}/manifests/${commit}`)} >/dev/null`,
"docker pull -q \"$registry_image\" >/dev/null",
"label_commit=$(docker image inspect \"$registry_image\" --format '{{ index .Config.Labels \"unidesk.ai/source-commit\" }}')",
@@ -159,7 +159,7 @@ export function d601K3sArtifactDeployScript(options: ArtifactRegistryOptions, sp
" raise SystemExit('manifest missing deployment(s): ' + ','.join(missing))",
"PY",
"if [ -n \"$apply_selector\" ]; then kubectl apply -f \"$manifest\" -l \"$apply_selector\"; else kubectl apply -f \"$manifest\"; fi",
...(environment === "dev" && spec.serviceId === "frontend" ? [d601DevFrontendAuthPatchScript(readConfig())] : []),
...(environment === "dev" && spec.serviceId === "frontend" ? [devFrontendAuthPatchScript(readConfig())] : []),
"previous_commit=$(kubectl -n \"$namespace\" get deployment \"$deployment\" -o jsonpath='{.metadata.annotations.unidesk\\.ai/deploy-commit}' 2>/dev/null || true)",
"python3 - \"$deployment_specs\" <<'PY' | while IFS=$'\\t' read -r rollout_deployment rollout_container; do",
"import json, sys",
@@ -264,7 +264,7 @@ export function d601K3sArtifactDeployScript(options: ArtifactRegistryOptions, sp
].join("\n");
}
export async function deployD601K3sArtifactNow(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget): Promise<Record<string, unknown>> {
export async function deployK3sTargetArtifactNow(options: ArtifactRegistryOptions, spec: ArtifactConsumerSpec, target: ArtifactConsumerTarget): Promise<Record<string, unknown>> {
const environment = options.environment ?? "prod";
const commit = options.commit;
if (commit === null) throw new Error("artifact-registry deploy-service requires --commit <full-sha>");
@@ -283,7 +283,7 @@ export async function deployD601K3sArtifactNow(options: ArtifactRegistryOptions,
registryProbe: commandTail(registryProbe),
};
}
const deployScript = d601K3sArtifactDeployScript(options, spec, target, commit);
const deployScript = k3sTargetArtifactDeployScript(options, spec, target, commit);
const deploy = await runRemoteScriptBackground(options, deployScript, Math.max(options.timeoutMs, 420_000), "d601-k3s-deploy");
if (deploy.exitCode !== 0 || deploy.timedOut) {
return {
+1 -1
View File
@@ -21,7 +21,7 @@ import {
type DeployJsonExecutorMirror,
type DeployJsonServiceContract,
} from "../deploy-json-contract";
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
import { k3sGuardShellLines } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import type { ArtifactDeployEnvironment, ArtifactRegistryOptions } from "./types";
+1 -1
View File
@@ -21,7 +21,7 @@ import {
type DeployJsonExecutorMirror,
type DeployJsonServiceContract,
} from "../deploy-json-contract";
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
import { k3sGuardShellLines } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import { artifactRegistryOptionsTargetSummary, type ArtifactRegistryCommandRuntime, type ArtifactRegistryOptions, type ArtifactRegistryReadonlyProbe } from "./types";
+1 -1
View File
@@ -21,7 +21,7 @@ import {
type DeployJsonExecutorMirror,
type DeployJsonServiceContract,
} from "../deploy-json-contract";
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
import { k3sGuardShellLines } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import type { ArtifactRegistryCommandRuntime, ArtifactRegistryOptions } from "./types";
@@ -20,7 +20,7 @@ import {
type DeployJsonExecutorMirror,
type DeployJsonServiceContract,
} from "../deploy-json-contract";
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
import { k3sGuardShellLines } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import type { ArtifactConsumerTarget, ArtifactDeployEnvironment, ArtifactRegistryOptions, AuthHealthGate, ComposeArtifactRuntime, RuntimeSecretContract, RuntimeSecretPresence, RuntimeSecretRequirement, RuntimeSecretSource } from "./types";
@@ -187,7 +187,7 @@ export function upsertEnvFileValues(path: string, values: Record<string, string>
writeFileSync(path, `${lines.join("\n")}\n`, "utf8");
}
export async function pullArtifactFromD601(options: ArtifactRegistryOptions, sourceImage: string): Promise<CommandResult> {
export async function pullArtifactFromRegistryTarget(options: ArtifactRegistryOptions, sourceImage: string): Promise<CommandResult> {
const localArchive = `/tmp/unidesk-artifact-${safeName(options.serviceId ?? "service")}-${safeName(options.commit ?? "commit")}-${Date.now().toString(36)}.tar.gz`;
const remoteArchive = `/tmp/unidesk-artifact-${safeName(options.serviceId ?? "service")}-${safeName(options.commit ?? "commit")}-${Date.now().toString(36)}.tar.gz`;
const remoteScript = [
+1 -1
View File
@@ -21,7 +21,7 @@ import {
type DeployJsonExecutorMirror,
type DeployJsonServiceContract,
} from "../deploy-json-contract";
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
import { k3sGuardShellLines } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import { artifactRegistryOptionsTargetSummary, type ArtifactRegistryFailureClassification, type ArtifactRegistryOptions, type RenderedBundle } from "./types";
+1 -1
View File
@@ -21,7 +21,7 @@ import {
type DeployJsonExecutorMirror,
type DeployJsonServiceContract,
} from "../deploy-json-contract";
import { d601K3sGuardShellLines } from "../d601-k3s-guard";
import { k3sGuardShellLines } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import { artifactRegistryTargetSummary, resolveArtifactRegistryTarget, type TargetConfigSource } from "../ops/targets";
+4 -4
View File
@@ -3,7 +3,7 @@ import { extname } from "node:path";
import { runCommandObserved, type CommandProgress } from "./command";
import { type UniDeskConfig, repoRoot, rootPath } from "./config";
import { composeConfig } from "./docker";
import { compactD601RecoveryGuardrails, runD601RecoveryGuardrails } from "./recovery-guardrails";
import { compactRecoveryGuardrails, runRecoveryGuardrails } from "./recovery-guardrails";
interface CheckItem {
name: string;
@@ -21,7 +21,7 @@ const syntaxFiles = [
"scripts/src/code-queue.ts",
"scripts/src/code-queue-execution-plane.ts",
"scripts/src/command.ts",
"scripts/src/d601-k3s-guard.ts",
"scripts/src/k3s-target-guard.ts",
"scripts/src/hwlab-cd.ts",
"scripts/src/decision-center.ts",
"scripts/src/dev-env.ts",
@@ -356,8 +356,8 @@ function skippedItem(name: string, reason: string, enableWith: string): CheckIte
return { name, ok: true, detail: { skipped: true, reason, enableWith } };
}
export function runRecoveryGuardrailsCheck(config: UniDeskConfig): ReturnType<typeof compactD601RecoveryGuardrails> {
return compactD601RecoveryGuardrails(runD601RecoveryGuardrails(config));
export function runRecoveryGuardrailsCheck(config: UniDeskConfig): ReturnType<typeof compactRecoveryGuardrails> {
return compactRecoveryGuardrails(runRecoveryGuardrails(config));
}
export async function runChecks(config: UniDeskConfig, options: CheckOptions = defaultCheckOptions): Promise<CheckRunResult> {
+1 -1
View File
@@ -18,7 +18,7 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import type { ArtifactSummary, ArtifactSummaryContext } from "./types";
+1 -1
View File
@@ -19,7 +19,7 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import type { CiCleanupFailedPodsOptions, CiCleanupRunsOptions, CiTarget } from "./types";
+1 -1
View File
@@ -18,7 +18,7 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import type { CiDevE2EOptions, DeployDevManifestSummary, DispatchResult } from "./types";
+1 -1
View File
@@ -18,7 +18,7 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import type { ArtifactSummary, ArtifactSummaryContext, CiPublishUserServiceArtifactOptions } from "./types";
+1 -1
View File
@@ -19,7 +19,7 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import { cleanupFailedPods, cleanupRuns, status } from "./cleanup";
+1 -1
View File
@@ -19,7 +19,7 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import { g14CiPipelineManifest, tektonPipelineReleaseUrl, tektonPipelineVersion, tektonTriggersInterceptorsUrl, tektonTriggersReleaseUrl, tektonTriggersVersion } from "./types";
+1 -1
View File
@@ -19,7 +19,7 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import type { CiInstallOptions } from "./types";
+1 -1
View File
@@ -19,7 +19,7 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import type { CiLogsOptions, CiTarget } from "./types";
+9 -9
View File
@@ -18,11 +18,11 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import type { CiCleanupFailedPodsOptions, CiCleanupRunsOptions, CiLogsOptions, CiPublishTransport, CiTarget } from "./types";
import { ciTarget, d601ProviderId, providerIdOption } from "./types";
import { ciTarget, defaultCiProviderId, providerIdOption } from "./types";
export function stringOption(args: string[], name: string): string | null {
const index = args.indexOf(name);
@@ -171,8 +171,8 @@ export function shellQuote(value: string): string {
}
export function ciTargetGuardShellLines(target: CiTarget, options: { passOutput?: "stdout" | "stderr" | "quiet" } = {}): string[] {
if (target.providerId === d601ProviderId) {
return d601K3sGuardShellLines(target.kubeconfig, options);
if (target.providerId === defaultCiProviderId) {
return k3sGuardShellLines(target.kubeconfig, options);
}
const passOutput = options.passOutput ?? "stdout";
const labelSelector = target.requiredNodeLabel === undefined ? "" : `${target.requiredNodeLabel.key}=${target.requiredNodeLabel.value}`;
@@ -320,11 +320,11 @@ export function userServicePublishBoundaryBlock(
): Record<string, unknown> | null {
const configService = config.microservices.find((item) => item.id === serviceId);
if (configService === undefined) return null;
const isD601K3sService = configService.providerId === d601ProviderId
&& configService.development.providerId === d601ProviderId
const isTargetK3sService = configService.providerId === defaultCiProviderId
&& configService.development.providerId === defaultCiProviderId
&& configService.deployment.mode === "k3sctl-managed";
const isD601DirectService = configService.providerId === d601ProviderId
&& configService.development.providerId === d601ProviderId
const isTargetDirectService = configService.providerId === defaultCiProviderId
&& configService.development.providerId === defaultCiProviderId
&& configService.deployment.mode === "unidesk-direct";
const isMainServerDirectService = configService.providerId === "main-server"
&& configService.development.providerId === "main-server"
@@ -332,7 +332,7 @@ export function userServicePublishBoundaryBlock(
const isMainServerInternalSidecar = configService.providerId === "main-server"
&& configService.development.providerId === "main-server"
&& configService.deployment.mode === "internal-sidecar";
if (isD601K3sService || isD601DirectService || isMainServerDirectService || isMainServerInternalSidecar) return null;
if (isTargetK3sService || isTargetDirectService || isMainServerDirectService || isMainServerInternalSidecar) return null;
return {
ok: false,
status: "blocked",
+1 -1
View File
@@ -18,7 +18,7 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import type { DispatchResult, PipelineRunCondition } from "./types";
+1 -1
View File
@@ -18,7 +18,7 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import type { CiOptions, CiPublishBackendCoreOptions, CiPublishUserServiceArtifactOptions } from "./types";
+3 -3
View File
@@ -18,12 +18,12 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import type { DispatchResult, PublishPreflight, PublishPreflightChannelProbe, PublishPreflightControlChannel, PublishPreflightControlChannelProbe, PublishPreflightDetailedChannel, PublishPreflightFailureClassification, PublishPreflightTransport } from "./types";
import { status } from "./cleanup";
import { d601ProviderId } from "./types";
import { defaultCiProviderId } from "./types";
export function chunks(value: string, size: number): string[] {
const result: string[] = [];
@@ -170,7 +170,7 @@ export function publishPreflightControlPlane(
fallbackRunnerPath: "main-server-local-docker",
remoteCommandShape,
providerTunnel: {
providerId: d601ProviderId,
providerId: defaultCiProviderId,
command: "host.ssh",
requiredFor: ["provider-host-ssh", "artifact-registry"],
},
+5 -5
View File
@@ -19,7 +19,7 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import type { ArtifactSummary, ArtifactSummaryContext, CiPublishBackendCoreOptions, CiPublishUserServiceArtifactOptions, PublishPreflight, PublishPreflightChannelProbe, PublishPreflightTransport } from "./types";
@@ -29,7 +29,7 @@ import { shellQuote } from "./options";
import { asRecord, asString, backendCoreUnavailable, channelProbe, classifyPublishPreflightFailure, coreBody, publishPreflightControlPlane, recommendedPublishPreflightAction, responseOk, summarizePublishControlChannels } from "./preflight";
import { runRemoteKubectlRaw } from "./remote";
import { backendCoreCiRunnerReady, backendCoreRemoteCommandShape, ciRunnerPreflightScript, commandResultFromDispatch, dispatchPreflightFailure } from "./runner-preflight";
import { ciTarget, d601Kubeconfig, d601ProviderId } from "./types";
import { ciTarget, defaultCiKubeconfig, defaultCiProviderId } from "./types";
export function assertArtifactSummaryComplete(artifact: ArtifactSummary, pipelineRun: string): void {
const missing = missingArtifactSummaryFields(artifact);
@@ -44,7 +44,7 @@ export async function publishUserServicePreflight(
plannedArtifact: ArtifactSummary,
transport: PublishPreflightTransport,
): Promise<PublishPreflight> {
const providerId = d601ProviderId;
const providerId = defaultCiProviderId;
const channels: PublishPreflightChannelProbe[] = [];
const overview = await transport.coreFetch("/api/overview", { maxResponseBytes: 500_000 });
const overviewBody = coreBody(overview);
@@ -66,7 +66,7 @@ export async function publishUserServicePreflight(
const probeScript = [
"set -euo pipefail",
...d601K3sGuardShellLines(d601Kubeconfig),
...k3sGuardShellLines(defaultCiKubeconfig),
"printf 'provider_host_ssh=ok\\n'",
"command -v bash >/dev/null",
"command -v docker >/dev/null",
@@ -144,7 +144,7 @@ export async function publishBackendCorePreflight(
plannedArtifact: ArtifactSummary,
transport: PublishPreflightTransport,
): Promise<PublishPreflight> {
const providerId = d601ProviderId;
const providerId = defaultCiProviderId;
const channels: PublishPreflightChannelProbe[] = [];
const overview = await transport.coreFetch("/api/overview", { maxResponseBytes: 500_000 });
const overviewBody = coreBody(overview);
+8 -8
View File
@@ -19,7 +19,7 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import type { ArtifactSummaryContext, CiOptions, CiPublishBackendCoreOptions, CiPublishUserServiceArtifactOptions, PublishPreflightTransport } from "./types";
@@ -33,7 +33,7 @@ import { assertArtifactSummaryComplete, publishBackendCorePreflight, publishUser
import { localPublishPreflightTransport } from "./remote";
import { backendCoreArtifactRequirements, backendCoreDevApplyPath } from "./runner-preflight";
import { prepareBackendCoreArtifactSource, prepareClaudeqqArtifactSource, prepareUserServiceArtifactSource } from "./source-prepare";
import { d601ProviderId, providerGatewayWsEgressProxyUrl } from "./types";
import { defaultCiProviderId, providerGatewayWsEgressProxyUrl } from "./types";
export async function run(options: CiOptions): Promise<Record<string, unknown>> {
const name = await remoteCreatePipelineRun(pipelineRunManifest(options), options.target);
@@ -152,7 +152,7 @@ export async function publishUserServiceArtifact(config: UniDeskConfig, options:
source: {
ok: preflight.ok,
mode: "planned-only",
providerId: d601ProviderId,
providerId: defaultCiProviderId,
repoUrl: options.repoUrl,
repoFetchUrl: plannedRepoFetchUrl,
repoProbeUrl: repoConnectivityProbeUrl(plannedRepoFetchUrl),
@@ -286,7 +286,7 @@ export async function runCiPublishUserServiceDryRunPreflight(
source: {
ok: preflight.ok,
mode: "planned-only",
providerId: d601ProviderId,
providerId: defaultCiProviderId,
repoUrl: options.repoUrl,
repoFetchUrl: plannedRepoFetchUrl,
repoProbeUrl: repoConnectivityProbeUrl(plannedRepoFetchUrl),
@@ -357,9 +357,9 @@ export async function runCiPublishBackendCoreDryRunPreflight(
targetCommit: options.commit,
sourceRepo: options.repoUrl,
serviceId: "backend-core",
providerId: d601ProviderId,
providerId: defaultCiProviderId,
ciRunner: {
providerId: d601ProviderId,
providerId: defaultCiProviderId,
environment: "D601",
namespace: "unidesk-ci",
pipeline: "unidesk-backend-core-artifact-publish",
@@ -386,7 +386,7 @@ export async function runCiPublishBackendCoreDryRunPreflight(
source: {
ok: preflight.ok,
mode: "planned-only",
providerId: d601ProviderId,
providerId: defaultCiProviderId,
repoUrl: options.repoUrl,
repoFetchUrl: plannedRepoFetchUrl,
repoProbeUrl: repoConnectivityProbeUrl(plannedRepoFetchUrl),
@@ -403,7 +403,7 @@ export async function runCiPublishBackendCoreDryRunPreflight(
identityRequired: repoNeedsGithubSshIdentity(plannedRepoFetchUrl),
},
d601Ci: {
providerId: d601ProviderId,
providerId: defaultCiProviderId,
namespace: "unidesk-ci",
pipeline: "unidesk-backend-core-artifact-publish",
wouldBuildOnD601: true,
+1 -1
View File
@@ -18,7 +18,7 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import type { DispatchResult, PublishPreflightTransport } from "./types";
+3 -3
View File
@@ -18,19 +18,19 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import type { ArtifactSummary, CiPublishBackendCoreOptions, DispatchResult } from "./types";
import { shellQuote } from "./options";
import { backendCoreUnavailable } from "./preflight";
import { dispatchSsh } from "./remote";
import { d601Kubeconfig } from "./types";
import { defaultCiKubeconfig } from "./types";
export function ciRunnerPreflightScript(sourceHostPath: string): string {
return [
"set -eu",
...d601K3sGuardShellLines(d601Kubeconfig),
...k3sGuardShellLines(defaultCiKubeconfig),
"printf 'provider_host_ssh=ok\\n'",
"printf 'kubectl='",
"command -v kubectl >/dev/null && printf 'ok\\n' || { printf 'missing\\n'; exit 127; }",
+7 -7
View File
@@ -18,20 +18,20 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import type { CiPublishBackendCoreOptions, CiPublishUserServiceArtifactOptions } from "./types";
import { repoConnectivityProbeUrl, repoNeedsGithubSshIdentity, repoSshUrl, requireRepoRelativePath, safePathToken, shellQuote } from "./options";
import { runRemoteBackground } from "./remote";
import { d601ProviderId, providerGatewayWsEgressProxyUrl } from "./types";
import { defaultCiProviderId, providerGatewayWsEgressProxyUrl } from "./types";
export async function prepareBackendCoreArtifactSource(config: UniDeskConfig, options: CiPublishBackendCoreOptions): Promise<Record<string, unknown>> {
const sourceRoot = "/home/ubuntu/.unidesk/ci/backend-core-artifacts";
const sourceHostPath = options.sourceHostPath;
const repoCache = "/home/ubuntu/.unidesk/ci/git/unidesk.git";
const repoFetchUrl = repoSshUrl(options.repoUrl);
const sshIdentity = repoNeedsGithubSshIdentity(repoFetchUrl) ? await ensureGithubSshIdentityForProvider(config, d601ProviderId) : null;
const sshIdentity = repoNeedsGithubSshIdentity(repoFetchUrl) ? await ensureGithubSshIdentityForProvider(config, defaultCiProviderId) : null;
if (sshIdentity !== null && !sshIdentity.ok) throw new Error(sshIdentity.detail);
const proxyPython = gitSshHttpConnectProxySource();
const dockerfile = requireRepoRelativePath(options.dockerfile, "CI.json.artifacts.backend-core.source.dockerfile");
@@ -82,7 +82,7 @@ export async function prepareBackendCoreArtifactSource(config: UniDeskConfig, op
return {
ok: true,
mode: repoNeedsGithubSshIdentity(repoFetchUrl) ? "d601-host-git-ssh-export" : "d601-host-git-https-export",
providerId: d601ProviderId,
providerId: defaultCiProviderId,
repoUrl: options.repoUrl,
repoFetchUrl,
commit: options.commit,
@@ -102,7 +102,7 @@ export async function prepareUserServiceArtifactSource(config: UniDeskConfig, op
const repoCache = `/home/ubuntu/.unidesk/ci/git/${safePathToken(options.serviceId)}.git`;
const repoFetchUrl = repoSshUrl(options.repoUrl);
const repoProbeUrl = repoConnectivityProbeUrl(repoFetchUrl);
const sshIdentity = repoNeedsGithubSshIdentity(repoFetchUrl) ? await ensureGithubSshIdentityForProvider(config, d601ProviderId) : null;
const sshIdentity = repoNeedsGithubSshIdentity(repoFetchUrl) ? await ensureGithubSshIdentityForProvider(config, defaultCiProviderId) : null;
if (sshIdentity !== null && !sshIdentity.ok) throw new Error(sshIdentity.detail);
const proxyPython = gitSshHttpConnectProxySource();
const script = [
@@ -156,7 +156,7 @@ export async function prepareUserServiceArtifactSource(config: UniDeskConfig, op
return {
ok: true,
mode: repoNeedsGithubSshIdentity(repoFetchUrl) ? "d601-host-git-ssh-export" : "d601-host-git-https-export",
providerId: d601ProviderId,
providerId: defaultCiProviderId,
repoUrl: options.repoUrl,
repoFetchUrl,
repoProbeUrl,
@@ -246,7 +246,7 @@ export async function prepareClaudeqqArtifactSource(config: UniDeskConfig, optio
return {
ok: true,
mode: "d601-host-gitee-https-export-with-unidesk-overlay",
providerId: d601ProviderId,
providerId: defaultCiProviderId,
repoUrl: options.repoUrl,
repoFetchUrl,
commit: options.commit,
+5 -5
View File
@@ -19,19 +19,19 @@ import {
parseArtifactRegistryOptions,
type ArtifactRegistryReadonlyProbe,
} from "../artifact-registry";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { runSshCommandCapture } from "../ssh";
import { cicdTargetSummary, resolveCicdTarget, type TargetConfigSource } from "../ops/targets";
import { status } from "./cleanup";
import { stringOption } from "./options";
const d601YamlTarget = resolveCicdTarget("D601");
const defaultCicdTarget = resolveCicdTarget(null);
const g14YamlTarget = resolveCicdTarget("G14");
export const d601ProviderId = d601YamlTarget.providerId;
export const defaultCiProviderId = defaultCicdTarget.providerId;
export const d601Kubeconfig = d601YamlTarget.kubeconfig;
export const defaultCiKubeconfig = defaultCicdTarget.kubeconfig;
export const tektonPipelineVersion = "v1.12.0";
@@ -51,7 +51,7 @@ export const codeQueueDirectDockerBaseImage = "unidesk-code-queue:d601";
export const providerDispatchCompletionLagMs = 45_000;
export const defaultCiPipelineManifest = d601YamlTarget.pipelineManifest;
export const defaultCiPipelineManifest = defaultCicdTarget.pipelineManifest;
export const g14CiPipelineManifest = g14YamlTarget.pipelineManifest;
+8 -8
View File
@@ -1,6 +1,6 @@
import { runCommand, type CommandResult } from "./command";
import { repoRoot } from "./config";
import { classifyD601K3sTarget, d601NativeKubeconfig, d601RequiredNodeName, type D601K3sGuardClassification } from "./d601-k3s-guard";
import { classifyK3sTarget, defaultNativeKubeconfig, requiredNativeNodeName, type K3sGuardClassification } from "./k3s-target-guard";
type PlaneStatus = "ready" | "blocked" | "degraded";
type SignalSeverity = "blocker" | "warning";
@@ -113,7 +113,7 @@ export interface CodeQueueExecutionPlaneObservation {
namespace: string;
kubeconfig: string;
worktreePath: string;
guard: D601K3sGuardClassification;
guard: K3sGuardClassification;
deployments: DeploymentObservation[];
pods: PodObservation[];
services: ServiceObservation[];
@@ -185,7 +185,7 @@ function parseExecutionPlaneOptions(args: string[]): ExecutionPlaneOptions {
const raw = hasFlag(args, "--raw");
return {
namespace: optionValue(args, ["--namespace"]) ?? expectedNamespace,
kubeconfig: optionValue(args, ["--kubeconfig"]) ?? d601NativeKubeconfig,
kubeconfig: optionValue(args, ["--kubeconfig"]) ?? defaultNativeKubeconfig,
worktreePath: optionValue(args, ["--worktree"]) ?? expectedWorktreePath,
full: raw || hasFlag(args, "--full"),
raw,
@@ -246,14 +246,14 @@ function runKubectl(args: string[], options: ExecutionPlaneOptions): ProbeResult
return runTran(["D601:k3s", "kubectl", ...args], options);
}
function collectGuard(options: ExecutionPlaneOptions): { guard: D601K3sGuardClassification; diagnostics: Record<string, unknown> } {
function collectGuard(options: ExecutionPlaneOptions): { guard: K3sGuardClassification; diagnostics: Record<string, unknown> } {
const context = runKubectl(["config", "current-context"], options);
const server = runKubectl(["config", "view", "--minify", "-o", "jsonpath={.clusters[0].cluster.server}"], options);
const nodes = runKubectl(["get", "nodes", "-o", "jsonpath={range .items[*]}{.metadata.name}{\"\\n\"}{end}"], options);
const combinedText = [context.stdout, context.stderr, server.stdout, server.stderr, nodes.stdout, nodes.stderr].join("\n");
const guard = classifyD601K3sTarget({
const guard = classifyK3sTarget({
kubeconfig: options.kubeconfig,
expectedKubeconfig: d601NativeKubeconfig,
expectedKubeconfig: defaultNativeKubeconfig,
currentContext: firstLine(context.stdout),
apiServer: firstLine(server.stdout),
nodeNames: lines(nodes.stdout),
@@ -433,7 +433,7 @@ function collectWorktree(options: ExecutionPlaneOptions): WorktreeObservation {
function collectResidual(): ResidualObservation {
const remoteOptions: ExecutionPlaneOptions = {
namespace: expectedNamespace,
kubeconfig: d601NativeKubeconfig,
kubeconfig: defaultNativeKubeconfig,
worktreePath: expectedWorktreePath,
full: false,
raw: false,
@@ -734,7 +734,7 @@ function evaluateObservation(observation: CodeQueueExecutionPlaneObservation, op
code: "d601-k3s-guard-blocked",
severity: "blocker",
message: observation.guard.summary,
expected: { kubeconfig: d601NativeKubeconfig, node: d601RequiredNodeName },
expected: { kubeconfig: defaultNativeKubeconfig, node: requiredNativeNodeName },
observed: { status: observation.guard.status, nodes: observation.guard.nodeNames },
}];
const signals = [...guardSignals, ...formalSignals, ...deploymentDriftSignals, ...deprecatedResidualSignals];
+3 -3
View File
@@ -13,7 +13,7 @@ import { baiduNetdiskRuntimeSecretRequirements, runtimeSecretContractFromEnvText
import { startJob } from "../jobs";
import { coreInternalFetch } from "../microservices";
import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "../code-queue-source-guard";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import {
compareDeployJsonExecutorMirrors,
@@ -34,7 +34,7 @@ import { asRecord, compactTail, elapsedMs, isUnideskRepo, nowIso, parseFullCommi
import { dockerBuildTimeoutMs, targetExportDir, targetIsMain, targetWorkDir } from "./paths";
import { healthVerify, manifestEnvironmentForService, pushStep, readRuntimeState, step } from "./remote";
import { buildDirectImageScript, buildImageScript, codeQueueSourcePreflightScript, composeDeployScript, ensureNativeK3sScript, imageLabelVerifyScript, importK3sImageScript, patchDevK3sManifestScript, prepareSourceScript, syncDevFrontendAuthScript, syncK8sControlManifestsScript, syncSourceScript } from "./scripts";
import { isD601MaintenanceDeployBlocked, isDevArtifactConsumerService, isDevK3sDeployService, isDirectComposeDeployMode, unsupportedReason } from "./service-plan";
import { isTargetSideMaintenanceDeployBlocked, isDevArtifactConsumerService, isDevK3sDeployService, isDirectComposeDeployMode, unsupportedReason } from "./service-plan";
export async function ensureGithubSshIdentityStep(config: UniDeskConfig, service: UniDeskMicroserviceConfig): Promise<StepResult> {
const startedAt = nowIso();
@@ -131,7 +131,7 @@ export async function applyOneService(config: UniDeskConfig, service: UniDeskMic
if (options.dryRun) return { ok: true, serviceId: service.id, action: "would-deploy", before, steps };
if (!options.dryRun && isD601MaintenanceDeployBlocked(service)) {
if (!options.dryRun && isTargetSideMaintenanceDeployBlocked(service)) {
return {
ok: false,
serviceId: service.id,
+1 -1
View File
@@ -13,7 +13,7 @@ import { baiduNetdiskRuntimeSecretRequirements, runtimeSecretContractFromEnvText
import { startJob } from "../jobs";
import { coreInternalFetch } from "../microservices";
import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "../code-queue-source-guard";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import {
compareDeployJsonExecutorMirrors,
+5 -5
View File
@@ -13,7 +13,7 @@ import { baiduNetdiskRuntimeSecretRequirements, runtimeSecretContractFromEnvText
import { startJob } from "../jobs";
import { coreInternalFetch } from "../microservices";
import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "../code-queue-source-guard";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import {
compareDeployJsonExecutorMirrors,
@@ -32,7 +32,7 @@ import type { DeployEnvironmentManifestSource, DeployManifest, DeployManifestSer
import { readProdDeployManifestSnapshot } from "./manifest";
import { environmentPlanServiceConfig, redactedSecretContractForService } from "./paths";
import { readRuntimeState } from "./remote";
import { artifactConsumerPlanKind, artifactConsumerPlanKindFromDeployJson, artifactConsumerPlanTarget, artifactConsumerPlanTargetFromDeployJson, artifactConsumerPlanValidation, codeQueueCiCdBoundary, codeQueueSelfBootstrapGuard, databaseFingerprint, deployJsonExecutorMirrors, deploymentPathForEnvironmentService, deploymentPathForEnvironmentServiceId, environmentTargetSummary, isD601MaintenanceDeployBlocked, selectServices, unsupportedEnvironmentPlanReason, unsupportedPlanTarget, unsupportedReason } from "./service-plan";
import { artifactConsumerPlanKind, artifactConsumerPlanKindFromDeployJson, artifactConsumerPlanTarget, artifactConsumerPlanTargetFromDeployJson, artifactConsumerPlanValidation, codeQueueCiCdBoundary, codeQueueSelfBootstrapGuard, databaseFingerprint, deployJsonExecutorMirrors, deploymentPathForEnvironmentService, deploymentPathForEnvironmentServiceId, environmentTargetSummary, isTargetSideMaintenanceDeployBlocked, selectServices, unsupportedEnvironmentPlanReason, unsupportedPlanTarget, unsupportedReason } from "./service-plan";
import { artifactConsumerDryRunBlockedServiceIds, deployEnvironmentTargets, devApplySupportedServiceIds, devArtifactConsumerProdDesiredFallbackServiceIds, devArtifactConsumerServiceIds, devArtifactLiveApplyBlockedServiceIds, prodArtifactConsumerServiceIds, prodArtifactLiveApplyBlockedServiceIds } from "./types";
export async function checkOrPlan(config: UniDeskConfig, manifest: DeployManifest, options: DeployOptions, action: "check" | "plan"): Promise<Record<string, unknown>> {
@@ -255,14 +255,14 @@ export function unsupportedDevApplyServices(manifest: DeployManifest, serviceId:
return services.map((service) => service.id).filter((id) => !devApplySupportedServiceIds.has(id) && !devArtifactConsumerServiceIds.has(id));
}
export function blockedD601MaintenanceDeployServices(config: UniDeskConfig, manifest: DeployManifest, serviceId: string | null): string[] {
export function blockedTargetSideMaintenanceDeployServices(config: UniDeskConfig, manifest: DeployManifest, serviceId: string | null): string[] {
return selectServices(config, manifest, serviceId)
.map((item) => item.config)
.filter(isD601MaintenanceDeployBlocked)
.filter(isTargetSideMaintenanceDeployBlocked)
.map((service) => service.id);
}
export function d601MaintenanceDeployBlockMessage(blocked: string[]): string {
export function targetSideMaintenanceDeployBlockMessage(blocked: string[]): string {
return `D601 target-side deployment is enabled only for k3sctl-adapter as a control-bridge maintenance path; artifact consumers must use registry CD. Blocked services: ${blocked.join(", ")}. Code Queue is dev-only through artifact-registry/deploy --env dev and has no production direct rollout path.`;
}
+6 -6
View File
@@ -14,7 +14,7 @@ import { baiduNetdiskRuntimeSecretRequirements, runtimeSecretContractFromEnvText
import { startJob } from "../jobs";
import { coreInternalFetch } from "../microservices";
import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "../code-queue-source-guard";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import {
compareDeployJsonExecutorMirrors,
@@ -32,7 +32,7 @@ import { resolveArtifactRegistryTarget } from "../ops/targets";
import type { DeployAction } from "./types";
import { applyJob, devArtifactApplyJob, prodArtifactApplyJob, runApplyNow, runArtifactConsumerApplyNow, runProdArtifactApplyNow } from "./artifact-jobs";
import { blockedD601MaintenanceDeployServices, checkOrPlan, commitOverrideArtifactConsumerError, d601MaintenanceDeployBlockMessage, devArtifactLiveApplyBlockedResult, environmentDryRunPlan, prodArtifactConsumerLocalManifestResult, prodArtifactUnsupportedResult, prodArtifactUnsupportedServices, selectedDevArtifactServicesWithProdFallback, selectedDevTargetServices, selectedEnvironmentServices, unsupportedDevApplyServices } from "./check-plan";
import { blockedTargetSideMaintenanceDeployServices, checkOrPlan, commitOverrideArtifactConsumerError, targetSideMaintenanceDeployBlockMessage, devArtifactLiveApplyBlockedResult, environmentDryRunPlan, prodArtifactConsumerLocalManifestResult, prodArtifactUnsupportedResult, prodArtifactUnsupportedServices, selectedDevArtifactServicesWithProdFallback, selectedDevTargetServices, selectedEnvironmentServices, unsupportedDevApplyServices } from "./check-plan";
import { readDeployManifest, readEnvironmentDeployManifest } from "./manifest";
import { deployHelp, isHelpArg, optionValue, parseOptions, resolveManifestCommits } from "./options";
import { devArtifactLiveApplyBlockedServiceIds, prodForbiddenTargetSideBuildServiceIds } from "./types";
@@ -87,8 +87,8 @@ export async function runDeployCommand(config: UniDeskConfig | null, args: strin
}
if (config === null) throw new Error("deploy apply --env dev requires config.json");
if (!options.dryRun) {
const blocked = blockedD601MaintenanceDeployServices(config, manifest, options.serviceId).filter((serviceId) => serviceId !== "decision-center");
if (blocked.length > 0) throw new Error(d601MaintenanceDeployBlockMessage(blocked));
const blocked = blockedTargetSideMaintenanceDeployServices(config, manifest, options.serviceId).filter((serviceId) => serviceId !== "decision-center");
if (blocked.length > 0) throw new Error(targetSideMaintenanceDeployBlockMessage(blocked));
}
if (options.dryRun || options.runNow) return await runApplyNow(config, manifest, options);
return applyJob(config, args, options);
@@ -96,8 +96,8 @@ export async function runDeployCommand(config: UniDeskConfig | null, args: strin
if (config === null) throw new Error("deploy local manifest mode requires config.json");
const rawManifest = await readDeployManifest(options.file);
if (action === "apply") {
const blocked = blockedD601MaintenanceDeployServices(config, rawManifest, options.serviceId);
if (blocked.length > 0) throw new Error(d601MaintenanceDeployBlockMessage(blocked));
const blocked = blockedTargetSideMaintenanceDeployServices(config, rawManifest, options.serviceId);
if (blocked.length > 0) throw new Error(targetSideMaintenanceDeployBlockMessage(blocked));
}
const manifest = resolveManifestCommits(rawManifest, options.serviceId);
if (action === "check" || action === "plan") return await checkOrPlan(config, manifest, options, action);
+3 -3
View File
@@ -13,7 +13,7 @@ import { baiduNetdiskRuntimeSecretRequirements, runtimeSecretContractFromEnvText
import { startJob } from "../jobs";
import { coreInternalFetch } from "../microservices";
import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "../code-queue-source-guard";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import {
compareDeployJsonExecutorMirrors,
@@ -29,7 +29,7 @@ import {
} from "../deploy-json-contract";
import type { DeployManifestService } from "./types";
import { asRecord, asString, d601K3sGuardScript, parseJsonObjectFromText, shellQuote } from "./options";
import { asRecord, asString, k3sTargetGuardScript, parseJsonObjectFromText, shellQuote } from "./options";
import { buildImageTag, k8sManifestPath, targetWorkDir } from "./paths";
import { runTargetCommand } from "./remote";
import { isCoreDeployService, isDevK3sDeployService } from "./service-plan";
@@ -84,7 +84,7 @@ export function applyK8sScript(service: UniDeskMicroserviceConfig): string {
: "";
return [
"set -euo pipefail",
d601K3sGuardScript(),
k3sTargetGuardScript(),
cleanup,
`kubectl apply -f ${shellQuote(manifest)}`,
].filter(Boolean).join("\n");
+1 -1
View File
@@ -13,7 +13,7 @@ import { baiduNetdiskRuntimeSecretRequirements, runtimeSecretContractFromEnvText
import { startJob } from "../jobs";
import { coreInternalFetch } from "../microservices";
import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "../code-queue-source-guard";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import {
compareDeployJsonExecutorMirrors,
+3 -3
View File
@@ -14,7 +14,7 @@ import { baiduNetdiskRuntimeSecretRequirements, runtimeSecretContractFromEnvText
import { startJob } from "../jobs";
import { coreInternalFetch } from "../microservices";
import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "../code-queue-source-guard";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import {
compareDeployJsonExecutorMirrors,
@@ -88,8 +88,8 @@ export function shellQuote(value: string): string {
return `'${value.replace(/'/gu, `'\\''`)}'`;
}
export function d601K3sGuardScript(): string {
return d601K3sGuardShellLines(k8sKubeconfig).join("\n");
export function k3sTargetGuardScript(): string {
return k3sGuardShellLines(k8sKubeconfig).join("\n");
}
export function compactTail(text: string, maxChars = 1600): string {
+1 -1
View File
@@ -13,7 +13,7 @@ import { baiduNetdiskRuntimeSecretRequirements, runtimeSecretContractFromEnvText
import { startJob } from "../jobs";
import { coreInternalFetch } from "../microservices";
import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "../code-queue-source-guard";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import {
compareDeployJsonExecutorMirrors,
+1 -1
View File
@@ -13,7 +13,7 @@ import { baiduNetdiskRuntimeSecretRequirements, runtimeSecretContractFromEnvText
import { startJob } from "../jobs";
import { coreInternalFetch } from "../microservices";
import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "../code-queue-source-guard";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import {
compareDeployJsonExecutorMirrors,
+4 -4
View File
@@ -13,7 +13,7 @@ import { baiduNetdiskRuntimeSecretRequirements, runtimeSecretContractFromEnvText
import { startJob } from "../jobs";
import { coreInternalFetch } from "../microservices";
import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "../code-queue-source-guard";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import {
compareDeployJsonExecutorMirrors,
@@ -29,7 +29,7 @@ import {
} from "../deploy-json-contract";
import type { DeployManifestService } from "./types";
import { d601K3sGuardScript, isUnideskRepo, providerSourceRepoUrl, safeId, shellQuote } from "./options";
import { k3sTargetGuardScript, isUnideskRepo, providerSourceRepoUrl, safeId, shellQuote } from "./options";
import { buildBaseImageFallbacks, buildCachePrelude, buildImageTag, devK3sPrepullImages, directBuildContextOverride, directComposeEnvFile, directComposeFile, directDockerfileOverride, k8sManifestPath, sourceBuildContext, sourceDockerfilePath, sourceFallbackWorktree, sourceProxyPrelude, sourceWorkDir, targetIsMain, targetRepoDir, targetWorkDir } from "./paths";
import { isDevK3sDeployService, isDirectComposeDeployMode } from "./service-plan";
import { k8sKubeconfig, nativeK3sCtrAddress, nativeK3sImage, nativeK3sInstallVersion, providerGatewayWsEgressProxyUrl } from "./types";
@@ -50,7 +50,7 @@ export function syncDevFrontendAuthScript(config: UniDeskConfig): string {
};
return [
"set -euo pipefail",
d601K3sGuardScript(),
k3sTargetGuardScript(),
`secret_patch=${shellQuote(JSON.stringify({ data }))}`,
`config_patch=${shellQuote(JSON.stringify({ data: runtimeConfig }))}`,
`kubectl -n unidesk-dev patch secret unidesk-dev-runtime-secrets --type merge -p "$secret_patch"`,
@@ -657,7 +657,7 @@ export function ensureNativeK3sScript(): string {
` if KUBECONFIG=${shellQuote(k8sKubeconfig)} kubectl get nodes >/dev/null 2>&1; then break; fi`,
" sleep 2",
"done",
d601K3sGuardScript(),
k3sTargetGuardScript(),
`KUBECONFIG=${shellQuote(k8sKubeconfig)} kubectl get nodes -l unidesk.ai/node-id=D601 --no-headers | grep -q .`,
`KUBECONFIG=${shellQuote(k8sKubeconfig)} kubectl wait --for=condition=Ready node -l unidesk.ai/node-id=D601 --timeout=180s`,
"install_system_images_from_legacy_k3s",
+9 -9
View File
@@ -14,7 +14,7 @@ import { baiduNetdiskRuntimeSecretRequirements, runtimeSecretContractFromEnvText
import { startJob } from "../jobs";
import { coreInternalFetch } from "../microservices";
import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "../code-queue-source-guard";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import {
compareDeployJsonExecutorMirrors,
@@ -31,7 +31,7 @@ import {
import type { ArtifactConsumerPlanKind, DeployEnvironment, DeployEnvironmentTarget, DeployManifest, DeployManifestService } from "./types";
import { targetIsMain, targetWorkDir } from "./paths";
import { artifactConsumerDryRunBlockedServiceIds, d601DeployNodeId, d601DeployProviderId, d601MaintenanceDeployAllowedServiceIds, devApplySupportedServiceIds, devArtifactConsumerServiceIds, k8sNamespace, prodArtifactConsumerServiceIds, prodArtifactLiveApplyBlockedServiceIds, unideskRepoUrl } from "./types";
import { artifactConsumerDryRunBlockedServiceIds, artifactDeployNodeId, artifactDeployProviderId, targetSideMaintenanceDeployAllowedServiceIds, devApplySupportedServiceIds, devArtifactConsumerServiceIds, k8sNamespace, prodArtifactConsumerServiceIds, prodArtifactLiveApplyBlockedServiceIds, unideskRepoUrl } from "./types";
export function frontendCoreDeployService(config: UniDeskConfig): UniDeskMicroserviceConfig {
return {
@@ -179,7 +179,7 @@ export function devK3sDeployService(id: string): UniDeskMicroserviceConfig | und
return {
id,
name: spec.name,
providerId: d601DeployProviderId,
providerId: artifactDeployProviderId,
description: spec.description,
repository: {
url: spec.repoUrl ?? unideskRepoUrl,
@@ -206,11 +206,11 @@ export function devK3sDeployService(id: string): UniDeskMicroserviceConfig | und
adapterServiceId: "k3sctl-adapter",
k3sServiceId: spec.composeService,
namespace: "unidesk-dev",
expectedNodeIds: [d601DeployNodeId],
activeNodeId: d601DeployNodeId,
expectedNodeIds: [artifactDeployNodeId],
activeNodeId: artifactDeployNodeId,
},
development: {
providerId: d601DeployProviderId,
providerId: artifactDeployProviderId,
sshPassthrough: true,
worktreePath: id === "code-queue"
? "/home/ubuntu/unidesk-dev-code-queue-deploy/code-queue"
@@ -243,10 +243,10 @@ export function isDevArtifactConsumerService(service: UniDeskMicroserviceConfig)
&& devArtifactConsumerServiceIds.has(service.id);
}
export function isD601MaintenanceDeployBlocked(service: UniDeskMicroserviceConfig): boolean {
export function isTargetSideMaintenanceDeployBlocked(service: UniDeskMicroserviceConfig): boolean {
if (targetIsMain(service)) return false;
if (service.providerId !== "D601") return false;
return !d601MaintenanceDeployAllowedServiceIds.has(service.id);
return !targetSideMaintenanceDeployAllowedServiceIds.has(service.id);
}
export function isDirectComposeDeployMode(service: UniDeskMicroserviceConfig): boolean {
@@ -279,7 +279,7 @@ export function selectServices(config: UniDeskConfig, manifest: DeployManifest,
export function unsupportedReason(service: UniDeskMicroserviceConfig): string | null {
if (service.repository.dockerfile.startsWith("docker.io/")) return "image-only service has no Dockerfile source artifact";
if (service.repository.composeFile.startsWith("docker run")) return "docker-run image-only service has no compose/k8s build path";
if (isD601MaintenanceDeployBlocked(service)) return "D601 maintenance-channel deployment is not allowed for this service; use reviewed registry artifact consumers instead";
if (isTargetSideMaintenanceDeployBlocked(service)) return "D601 maintenance-channel deployment is not allowed for this service; use reviewed registry artifact consumers instead";
if (service.repository.commitId === "local") return null;
return null;
}
+8 -8
View File
@@ -14,7 +14,7 @@ import { baiduNetdiskRuntimeSecretRequirements, runtimeSecretContractFromEnvText
import { startJob } from "../jobs";
import { coreInternalFetch } from "../microservices";
import { codeQueueSourceImportPreflight, codeQueueSourceSubdir } from "../code-queue-source-guard";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "../d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "../k3s-target-guard";
import { composeRuntimeEnvValue } from "../runtime-env";
import { resolveArtifactRegistryTarget } from "../ops/targets";
import {
@@ -153,11 +153,11 @@ export const shortRemoteTimeoutMs = 20_000;
export const providerDispatchCompletionLagMs = 45_000;
const d601ArtifactRegistryTarget = resolveArtifactRegistryTarget("D601");
const defaultArtifactRegistryTarget = resolveArtifactRegistryTarget(null);
export const d601DeployProviderId = d601ArtifactRegistryTarget.providerId;
export const artifactDeployProviderId = defaultArtifactRegistryTarget.providerId;
export const d601DeployNodeId = d601ArtifactRegistryTarget.targetId;
export const artifactDeployNodeId = defaultArtifactRegistryTarget.targetId;
export const pollIntervalMs = 5_000;
@@ -165,7 +165,7 @@ export const remoteDeployRoot = "/home/ubuntu/.unidesk/deploy";
export const k8sNamespace = "unidesk";
export const k8sKubeconfig = d601NativeKubeconfig;
export const k8sKubeconfig = defaultNativeKubeconfig;
export // Production k3s hostPath repo. Code Queue production Pods mount this path as /app and /root/unidesk,
// so deploy guards must validate this tree rather than config.json development.worktreePath.
@@ -181,7 +181,7 @@ export const nativeK3sCtrAddress = "/run/k3s/containerd/containerd.sock";
export const unideskRepoUrl = "https://github.com/pikasTech/unidesk";
export const d601MaintenanceDeployAllowedServiceIds = new Set<string>(["k3sctl-adapter"]);
export const targetSideMaintenanceDeployAllowedServiceIds = new Set<string>(["k3sctl-adapter"]);
export const devApplySupportedServiceIds = new Set<string>();
@@ -243,8 +243,8 @@ export const deployEnvironmentTargets: Record<DeployEnvironment, DeployEnvironme
name: "unidesk",
},
provider: {
providerId: d601DeployProviderId,
nodeId: d601DeployNodeId,
providerId: artifactDeployProviderId,
nodeId: artifactDeployNodeId,
credentialScope: "prod",
},
},
+4 -4
View File
@@ -1,7 +1,7 @@
import { readFileSync } from "node:fs";
import { runCommand } from "./command";
import { repoRoot, rootPath } from "./config";
import { d601K3sGuardShellLines, d601NativeKubeconfig } from "./d601-k3s-guard";
import { k3sGuardShellLines, defaultNativeKubeconfig } from "./k3s-target-guard";
import { startJob } from "./jobs";
const defaultManifest = "src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-foundation.k8s.yaml";
@@ -217,8 +217,8 @@ function validateDatabaseUrl(url: string): { ok: boolean; url: string; reason: s
}
function kubectlDryRun(manifestPath: string): unknown {
const kubeconfig = d601NativeKubeconfig;
const guardScript = d601K3sGuardShellLines(kubeconfig).join("\n");
const kubeconfig = defaultNativeKubeconfig;
const guardScript = k3sGuardShellLines(kubeconfig).join("\n");
const guard = runCommand(["sh", "-lc", guardScript], repoRoot, {
timeoutMs: 60_000,
env: { ...process.env, KUBECONFIG: kubeconfig },
@@ -283,7 +283,7 @@ function prewarmImagesScript(options: PrewarmImagesOptions): string {
`pull_missing=${options.pullMissing ? "1" : "0"}`,
`pull_timeout_seconds=${pullTimeoutSeconds}`,
"ctr_address=/run/k3s/containerd/containerd.sock",
...d601K3sGuardShellLines(),
...k3sGuardShellLines(),
"export DOCKER_CONFIG=/tmp/unidesk-dev-env-docker-config",
"mkdir -p \"$DOCKER_CONFIG\"",
"printf '{}\\n' > \"$DOCKER_CONFIG/config.json\"",
+8 -8
View File
@@ -356,7 +356,7 @@ export function composeConfig(config: UniDeskConfig): { runtimeEnv: ComposeRunti
export function startStack(config: UniDeskConfig): unknown {
const containers = dockerContainers(config);
const deprecatedEntry = deprecatedD601HostComposeEntry(config, composeRuntimeEnvPreview(config), containers);
const deprecatedEntry = deprecatedHostComposeEntry(config, composeRuntimeEnvPreview(config), containers);
if (deprecatedEntry !== null) return deprecatedEntry;
const runtimeEnv = writeComposeEnv(config, true);
const compose = resolveComposeCommand(config, runtimeEnv.envFile);
@@ -663,7 +663,7 @@ function dockerExec(config: UniDeskConfig, container: string, command: string[])
export async function stackStatus(config: UniDeskConfig): Promise<unknown> {
const containers = dockerContainers(config);
const deprecatedEntry = deprecatedD601HostComposeEntry(config, composeRuntimeEnvPreview(config), containers);
const deprecatedEntry = deprecatedHostComposeEntry(config, composeRuntimeEnvPreview(config), containers);
if (deprecatedEntry !== null) return deprecatedEntry;
const runtimeEnv = writeComposeEnv(config, false);
const runtimeRaw = existsSync(runtimeEnv.envFile) ? readFileSync(runtimeEnv.envFile, "utf8") : "";
@@ -739,16 +739,16 @@ export async function stackStatus(config: UniDeskConfig): Promise<unknown> {
};
}
export function deprecatedD601HostComposeEntryForTest(config: UniDeskConfig, runtimeEnv: ComposeRuntimeEnv, containers: ContainerStatus[], currentRoot: string): DeprecatedHostComposeEntry | null {
return deprecatedD601HostComposeEntry(config, runtimeEnv, containers, currentRoot);
export function deprecatedHostComposeEntryForTest(config: UniDeskConfig, runtimeEnv: ComposeRuntimeEnv, containers: ContainerStatus[], currentRoot: string): DeprecatedHostComposeEntry | null {
return deprecatedHostComposeEntry(config, runtimeEnv, containers, currentRoot);
}
function deprecatedD601HostComposeEntry(config: UniDeskConfig, runtimeEnv: ComposeRuntimeEnv, containers: ContainerStatus[], currentRoot = repoRoot): DeprecatedHostComposeEntry | null {
function deprecatedHostComposeEntry(config: UniDeskConfig, runtimeEnv: ComposeRuntimeEnv, containers: ContainerStatus[], currentRoot = repoRoot): DeprecatedHostComposeEntry | null {
const cwd = resolve(currentRoot);
const d601Workspaces = ["/home/ubuntu/unidesk", "/home/ubuntu/workspace/unidesk-dev", "/workspace/unidesk"];
const cwdLooksD601 = d601Workspaces.some((workspace) => cwd === workspace || cwd.startsWith(`${workspace}/.worktree/`));
const legacyHostWorkspaces = ["/home/ubuntu/unidesk", "/home/ubuntu/workspace/unidesk-dev", "/workspace/unidesk"];
const cwdLooksLegacyHost = legacyHostWorkspaces.some((workspace) => cwd === workspace || cwd.startsWith(`${workspace}/.worktree/`));
const upgradeRootLooksMainServer = config.providerGateway.upgrade.hostProjectRoot === "/root/unidesk";
if (!cwdLooksD601 || !upgradeRootLooksMainServer || containers.length > 0) return null;
if (!cwdLooksLegacyHost || !upgradeRootLooksMainServer || containers.length > 0) return null;
const publicPorts = fixedPorts(config);
const conflictingListeners = publicPorts
.filter((item) => item.listening)
+6 -6
View File
@@ -2281,7 +2281,7 @@ async function serviceChecks(config: UniDeskConfig, urls: PublicUrls, checks: E2
: null;
const filebrowserHealth = apiClient.getJson("/api/microservices/filebrowser/health");
const filebrowserWebui = apiClient.getJson("/api/microservices/filebrowser/proxy/");
const filebrowserD601Health = apiClient.getJson("/api/microservices/filebrowser-d601/health");
const filebrowserProviderHealth = apiClient.getJson("/api/microservices/filebrowser-d601/health");
const todoE2eName = `E2E Todo ${Date.now()}`;
const todoNoteCreate = apiClient.getJson("/api/microservices/todo-note/proxy/api/instances", { method: "POST", body: { name: todoE2eName } });
const todoCreatedId = (todoNoteCreate as { body?: { id?: string } }).body?.id ?? "";
@@ -2332,7 +2332,7 @@ async function serviceChecks(config: UniDeskConfig, urls: PublicUrls, checks: E2
const codeQueue = microserviceList.find((service) => service.id === "code-queue");
const decisionCenter = microserviceList.find((service) => service.id === "decision-center");
const filebrowser = microserviceList.find((service) => service.id === "filebrowser");
const filebrowserD601 = microserviceList.find((service) => service.id === "filebrowser-d601");
const filebrowserProvider = microserviceList.find((service) => service.id === "filebrowser-d601");
const findjobSummaryBody = (findjobSummary as { body?: { totalJobs?: number; prioritizedJobs?: number } }).body;
const findjobJobs = (findjobJobsPreview as { body?: { jobs?: unknown[]; _unidesk?: { arrayLimits?: { jobs?: { returnedLength?: number; originalLength?: number } } } } }).body;
const todoNoteRows = (todoNoteInstances as { body?: { instances?: Array<{ id?: string; name?: string; todoCount?: number; completedCount?: number }> } }).body?.instances ?? [];
@@ -2378,7 +2378,7 @@ async function serviceChecks(config: UniDeskConfig, urls: PublicUrls, checks: E2
const k3sctlClaudeqqService = k3sctlControlPlaneBody?.services?.find((service) => service.id === "claudeqq");
const k3sctlDecisionCenterService = k3sctlControlPlaneBody?.services?.find((service) => service.id === "decision-center");
const filebrowserHealthBody = (filebrowserHealth as { body?: { status?: string } }).body;
const filebrowserD601HealthBody = (filebrowserD601Health as { body?: { status?: string } }).body;
const filebrowserProviderHealthBody = (filebrowserProviderHealth as { body?: { status?: string } }).body;
const filebrowserWebuiText = String((filebrowserWebui as { body?: { text?: string } }).body?.text || "");
const firstPipelineRun = Array.isArray(pipelineSnapshotBody?.runs)
? pipelineSnapshotBody.runs[0] as { runId?: string; pipelineId?: string; status?: string; updatedAt?: string } | undefined
@@ -2493,11 +2493,11 @@ async function serviceChecks(config: UniDeskConfig, urls: PublicUrls, checks: E2
&& filebrowser?.providerId === "D518"
&& filebrowser.backend?.public === false
&& filebrowser.runtime?.container?.name === "unidesk-filebrowser-d518"
&& filebrowserD601?.providerId === "D601",
{ filebrowser, filebrowserD601 });
&& filebrowserProvider?.providerId === "D601",
{ filebrowser, filebrowserProvider });
addSelectedCheck(checks, options, "microservice:filebrowser-health", (filebrowserHealth as { ok?: boolean }).ok === true && filebrowserHealthBody?.status === "OK", filebrowserHealth);
addSelectedCheck(checks, options, "microservice:filebrowser-webui", (filebrowserWebui as { ok?: boolean; status?: number }).ok === true && (filebrowserWebui as { status?: number }).status === 200 && filebrowserWebuiText.includes("File Browser"), { status: (filebrowserWebui as { status?: number }).status, textPreview: filebrowserWebuiText.slice(0, 600) });
addSelectedCheck(checks, options, "microservice:filebrowser-d601-health", (filebrowserD601Health as { ok?: boolean }).ok === true && filebrowserD601HealthBody?.status === "OK", filebrowserD601Health);
addSelectedCheck(checks, options, "microservice:filebrowser-d601-health", (filebrowserProviderHealth as { ok?: boolean }).ok === true && filebrowserProviderHealthBody?.status === "OK", filebrowserProviderHealth);
addSelectedCheck(checks, options, "microservice:findjob-status", (findjobStatus as { ok?: boolean }).ok === true && (findjobStatus as { body?: { microservice?: { id?: string; providerId?: string } } }).body?.microservice?.providerId === "D601", findjobStatus);
addSelectedCheck(checks, options, "microservice:findjob-health", (findjobHealth as { ok?: boolean; body?: { ok?: boolean } }).ok === true && (findjobHealth as { body?: { ok?: boolean } }).body?.ok === true, findjobHealth);
addSelectedCheck(checks, options, "microservice:findjob-summary", (findjobSummary as { ok?: boolean }).ok === true && Number.isFinite(findjobSummaryBody?.totalJobs) && Number.isFinite(findjobSummaryBody?.prioritizedJobs), findjobSummary);
+2 -2
View File
@@ -760,7 +760,7 @@ function cicdHelpSummary(): unknown {
};
}
function hwlabG14HelpSummary(): unknown {
function hwlabLegacyRuntimeHelpSummary(): unknown {
return {
command: "hwlab g14 retirement",
output: "json",
@@ -840,7 +840,7 @@ export async function staticNamespaceHelp(args: string[]): Promise<unknown | nul
return loadHelp(async () => (await import("./hwlab-node-hwpod-preinstall")).hwlabNodeHwpodPreinstallHelp(), hwlabNodeHelpSummary());
}
if (top === "hwlab" && (sub === "node" || sub === "nodes")) return loadHelp(async () => (await import("./hwlab-node")).hwlabNodeHelp(), hwlabNodeHelpSummary());
if (top === "hwlab" && sub === "g14") return loadHelp(async () => (await import("./hwlab-g14")).hwlabG14Help(), hwlabG14HelpSummary());
if (top === "hwlab" && sub === "g14") return loadHelp(async () => (await import("./hwlab-legacy-runtime")).hwlabLegacyRuntimeHelp(), hwlabLegacyRuntimeHelpSummary());
if (top === "hwlab") return loadHelp(async () => (await import("./hwlab-cd")).hwlabHelp(), hwlabHelpSummary());
return null;
}
+4 -4
View File
@@ -5,7 +5,7 @@ import { mkdir } from "node:fs/promises";
import { join } from "node:path";
import { gzipSync } from "node:zlib";
import { readConfig, repoRoot, rootPath, type UniDeskConfig } from "./config";
import { d601NativeKubeconfig } from "./d601-k3s-guard";
import { defaultNativeKubeconfig } from "./k3s-target-guard";
type HwlabCdAction = "status" | "preflight" | "audit" | "apply";
type HwlabCdEnvironment = "dev";
@@ -117,7 +117,7 @@ function parseOptions(args: string[]): HwlabCdOptions {
environment: "dev",
dryRun: false,
repoPath: null,
kubeconfig: d601NativeKubeconfig,
kubeconfig: defaultNativeKubeconfig,
providerId: defaultProviderId,
transport: envTransport(),
mainServerHost: process.env.UNIDESK_MAIN_SERVER_IP ?? process.env.UNIDESK_MAIN_SERVER_HOST ?? null,
@@ -140,7 +140,7 @@ function parseOptions(args: string[]): HwlabCdOptions {
index += 1;
} else if (arg === "--kubeconfig") {
const value = readOption(args, index + 1, arg);
if (value !== d601NativeKubeconfig) throw new Error(`hwlab cd requires the D601 native k3s kubeconfig: ${d601NativeKubeconfig}`);
if (value !== defaultNativeKubeconfig) throw new Error(`hwlab cd requires the D601 native k3s kubeconfig: ${defaultNativeKubeconfig}`);
options.kubeconfig = value;
index += 1;
} else if (arg === "--provider-id") {
@@ -783,7 +783,7 @@ export function hwlabHelp(): Record<string, unknown> {
],
description: "Bounded UniDesk wrapper for the HWLAB DEV CD path. The default transport uploads a small remote runner to D601 through short host.ssh commands, creates a one-run HWLAB clone owned by the host.ssh user, then calls HWLAB repo-owned scripts/dev-cd-apply.mjs from the D601 side.",
boundary: [
`KUBECONFIG is forced to ${d601NativeKubeconfig}`,
`KUBECONFIG is forced to ${defaultNativeKubeconfig}`,
"docker-desktop, desktop-control-plane, and 127.0.0.1:11700 are refusal signals for the explicit D601 target",
`default HWLAB CD repo is ${defaultHwlabCdRepoPath}, materialized from dedicated full bare cache ${defaultHwlabCdCachePath}; ${rejectedRunnerHistoryRepoPath} is rejected as runner history`,
"deploy/deploy.json remains the authoritative desired-state source",
+9 -9
View File
@@ -79,11 +79,11 @@ export function fakeModelProviderHelp(): Record<string, unknown> {
command: "hwlab nodes fake-model-provider",
description: "YAML-first fake OpenAI-compatible Responses provider for HWLAB/AgentRun sentinel smoke.",
examples: [
"bun scripts/cli.ts hwlab nodes fake-model-provider plan --node D518 --lane v03 --provider fake-echo",
"bun scripts/cli.ts hwlab nodes fake-model-provider materialize --node D518 --lane v03 --provider fake-echo --confirm",
"bun scripts/cli.ts hwlab nodes fake-model-provider apply --node D518 --lane v03 --provider fake-echo --confirm",
"bun scripts/cli.ts hwlab nodes fake-model-provider status --node D518 --lane v03 --provider fake-echo",
"bun scripts/cli.ts hwlab nodes fake-model-provider smoke --node D518 --lane v03 --provider fake-echo",
"bun scripts/cli.ts hwlab nodes fake-model-provider plan --node NC01 --lane v03 --provider fake-echo",
"bun scripts/cli.ts hwlab nodes fake-model-provider materialize --node NC01 --lane v03 --provider fake-echo --confirm",
"bun scripts/cli.ts hwlab nodes fake-model-provider apply --node NC01 --lane v03 --provider fake-echo --confirm",
"bun scripts/cli.ts hwlab nodes fake-model-provider status --node NC01 --lane v03 --provider fake-echo",
"bun scripts/cli.ts hwlab nodes fake-model-provider smoke --node NC01 --lane v03 --provider fake-echo",
],
actions: {
plan: "Read YAML configRefs and show local source/materialization and target k3s objects without mutation.",
@@ -147,7 +147,7 @@ function parseFakeModelProviderOptions(args: string[]): FakeModelProviderOptions
throw new Error(`unsupported fake-model-provider option: ${arg}`);
}
const node = requiredOption(args, "--node");
if (!/^[A-Z][A-Z0-9_-]*$/u.test(node)) throw new Error("--node must be a simple node id such as D518");
if (!/^[A-Z][A-Z0-9_-]*$/u.test(node)) throw new Error("--node must be a simple node id such as NC01");
const laneRaw = requiredOption(args, "--lane");
if (!isHwlabRuntimeLane(laneRaw)) throw new Error(`--lane must be one of v02, v03; got ${laneRaw}`);
const provider = optionValue(args, "--provider") ?? "fake-echo";
@@ -172,7 +172,7 @@ function parseFakeModelProviderOptions(args: string[]): FakeModelProviderOptions
function readFakeModelProviderState(options: FakeModelProviderOptions): FakeModelProviderState {
const spec = hwlabRuntimeLaneSpecForNode(options.lane, options.node);
const configPath = rootPath("config", "hwlab-fake-model-provider", `${options.node.toLowerCase()}-${options.lane}`, `${options.provider}.yaml`);
const configPath = rootPath("config", "hwlab-fake-model-provider", `${options.provider}.yaml`);
if (!existsSync(configPath)) throw new Error(`fake provider config is missing: ${repoRelative(configPath)}`);
const root = readYamlRecord(configPath, "HwlabFakeModelProvider");
const provider = record(root.provider, "provider");
@@ -249,7 +249,7 @@ function planFakeModelProvider(state: FakeModelProviderState): Record<string, un
materialize: `bun scripts/cli.ts hwlab nodes fake-model-provider materialize --node ${state.options.node} --lane ${state.options.lane} --provider ${state.options.provider} --confirm`,
apply: `bun scripts/cli.ts hwlab nodes fake-model-provider apply --node ${state.options.node} --lane ${state.options.lane} --provider ${state.options.provider} --confirm`,
agentrunSecretSync: `bun scripts/cli.ts agentrun control-plane secret-sync --node ${state.options.node} --lane ${state.agentrun.spec.lane} --confirm`,
sentinelPlan: `bun scripts/cli.ts web-probe sentinel plan --node ${state.options.node} --lane ${state.options.lane} --sentinel workbench-fake-echo-session-invariance-10x`,
sentinelPlan: `bun scripts/cli.ts web-probe sentinel plan --node ${state.options.node} --lane ${state.options.lane} --dry-run`,
},
valuesPrinted: false,
};
@@ -371,7 +371,7 @@ function applyFakeModelProvider(state: FakeModelProviderState): Record<string, u
next: {
smoke: `bun scripts/cli.ts hwlab nodes fake-model-provider smoke --node ${state.options.node} --lane ${state.options.lane} --provider ${state.options.provider}`,
agentrunSecretSync: `bun scripts/cli.ts agentrun control-plane secret-sync --node ${state.options.node} --lane ${state.agentrun.spec.lane} --confirm`,
sentinelTrigger: "bun scripts/cli.ts web-probe sentinel control-plane trigger-current --node D518 --lane v03 --sentinel workbench-fake-echo-session-invariance-10x --confirm --wait",
sentinelPlan: `bun scripts/cli.ts web-probe sentinel plan --node ${state.options.node} --lane ${state.options.lane} --dry-run`,
},
valuesPrinted: false,
};
-2
View File
@@ -1,2 +0,0 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. Thin compatibility entry for scripts/src/hwlab-g14.ts.
export * from "./hwlab-g14/index";
-363
View File
@@ -1,363 +0,0 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. types module for scripts/src/hwlab-g14.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:1-288 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { createHash, randomBytes } from "node:crypto";
import { repoRoot, rootPath, type Config } from "../config";
import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import { durationSeconds } from "./pr-monitor";
export const HWLAB_REPO = "pikasTech/HWLAB";
export const G14_SOURCE_BRANCH = "v0.3";
export const G14_PROVIDER = "NC01";
export const G14_WORKSPACE = "/root/hwlab-v03";
export const V02_LANE_SPEC = hwlabRuntimeLaneSpec("v02") ?? hwlabRuntimeLaneSpec("v03");
export const V02_SOURCE_BRANCH = V02_LANE_SPEC.sourceBranch;
export const V02_WORKSPACE = V02_LANE_SPEC.workspace;
export const V02_CICD_REPO = V02_LANE_SPEC.cicdRepo;
export const DEV_NAMESPACE = "hwlab-dev";
export const PROD_NAMESPACE = "hwlab-prod";
export const CI_NAMESPACE = "hwlab-ci";
export const ARGO_NAMESPACE = "argocd";
export const DEV_APP = "hwlab-g14-dev";
export const PROD_APP = "hwlab-g14-prod";
export const V02_APP = V02_LANE_SPEC.app;
export const V02_PIPELINE = V02_LANE_SPEC.pipeline;
export const V02_POLLER = "hwlab-v02-branch-poller";
export const V02_RECONCILER = "hwlab-v02-control-plane-reconciler";
export const V02_PIPELINERUN_PREFIX = V02_LANE_SPEC.pipelineRunPrefix;
export const V02_CONTROL_PLANE_FIELD_MANAGER = V02_LANE_SPEC.controlPlaneFieldManager;
export const V02_GIT_URL = V02_LANE_SPEC.gitUrl;
export const V02_GIT_READ_URL = V02_LANE_SPEC.gitReadUrl;
export const V02_GIT_WRITE_URL = V02_LANE_SPEC.gitWriteUrl;
export const V02_GITOPS_BRANCH = V02_LANE_SPEC.gitopsBranch;
export const V02_CATALOG_PATH = V02_LANE_SPEC.catalogPath;
export const V02_RUNTIME_PATH = V02_LANE_SPEC.runtimePath;
export const V02_RUNTIME_NAMESPACE = V02_LANE_SPEC.runtimeNamespace;
export const V02_OPENFGA_SECRET = "hwlab-v02-openfga";
export const V02_OPENFGA_AUTHN_SECRET_KEY = "authn-preshared-key";
export const V02_OPENFGA_DATASTORE_URI_SECRET_KEY = "datastore-uri";
export const V02_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY = "postgres-password";
export const V02_OPENFGA_DB_NAME = "hwlab_openfga";
export const V02_OPENFGA_DB_USER = "hwlab_openfga";
export const V02_MASTER_ADMIN_API_KEY_SECRET = "hwlab-v02-master-server-admin-api-key";
export const V02_MASTER_ADMIN_API_KEY_SECRET_KEY = "api-key";
export const V02_MASTER_ADMIN_API_KEY_LOCAL_ENV = "/root/.config/hwlab-v02/master-server-admin-api-key.env";
export const V02_REGISTRY_PREFIX = V02_LANE_SPEC.registryPrefix;
export const V02_BASE_IMAGE = V02_LANE_SPEC.baseImage;
export const GIT_MIRROR_NAMESPACE = "devops-infra";
export const GIT_MIRROR_MANIFEST_FIELD_MANAGER = "unidesk-hwlab-git-mirror";
export const GIT_MIRROR_SYNC_JOB_PREFIX = "git-mirror-hwlab-sync-manual";
export const GIT_MIRROR_LEGACY_CRONJOB = "git-mirror-hwlab-sync";
export const G14_OBSERVABILITY_NAMESPACE = "devops-infra";
export const G14_OBSERVABILITY_FIELD_MANAGER = "unidesk-g14-observability";
export const G14_PROMETHEUS_OPERATOR_VERSION = "v0.91.0";
export const G14_PROMETHEUS_VERSION = "v3.12.0";
export const G14_PROMETHEUS_NAME = "g14-shared";
export const G14_PROMETHEUS_SERVICE = "prometheus-g14-shared";
export const G14_PROMETHEUS_SERVICE_ACCOUNT = "g14-observability-prometheus";
export const G14_PROMETHEUS_OPERATOR_RELEASE_ASSET = `https://github.com/prometheus-operator/prometheus-operator/releases/download/${G14_PROMETHEUS_OPERATOR_VERSION}/bundle.yaml`;
export const V02_SERVICE_IDS = [...V02_LANE_SPEC.serviceIds];
export const V02_CLOUD_WEB_URL = V02_LANE_SPEC.publicWebUrl;
export const V02_CLOUD_API_URL = V02_LANE_SPEC.publicApiUrl;
export const V02_OBSERVABILITY_SERVICE_IDS = [
"hwlab-agent-skills",
"hwlab-cloud-api",
"hwlab-cloud-web",
"hwlab-deepseek-proxy",
"hwlab-edge-proxy",
];
export const V02_OBSERVABILITY_EXPECTED_TARGET_COUNT = V02_OBSERVABILITY_SERVICE_IDS.length;
export const V02_OBSERVABILITY_QUERIES = {
scrapeReachable: 'up{namespace="hwlab-v02"}',
sidecarServing: 'hwlab_service_up{namespace="hwlab-v02"}',
businessHealthProbe: 'hwlab_service_health_probe_success{namespace="hwlab-v02"}',
healthProbeConfigured: 'hwlab_service_health_probe_configured{namespace="hwlab-v02"}',
healthProbeStatusCode: 'hwlab_service_health_probe_status_code{namespace="hwlab-v02"}',
healthProbeDurationSeconds: 'hwlab_service_health_probe_duration_seconds{namespace="hwlab-v02"}',
processUptimeSeconds: 'hwlab_service_process_uptime_seconds{namespace="hwlab-v02"}',
scrapeDurationSeconds: 'scrape_duration_seconds{namespace="hwlab-v02"}',
scrapeSamplesScraped: 'scrape_samples_scraped{namespace="hwlab-v02"}',
};
export const V02_OBSERVABILITY_CLOSEOUT_QUERY_NAMES = ["scrapeReachable", "sidecarServing", "businessHealthProbe"] as const;
export const V02_OBSERVABILITY_BOOLEAN_QUERY_NAMES = [...V02_OBSERVABILITY_CLOSEOUT_QUERY_NAMES, "healthProbeConfigured"];
export function v02PipelineServiceIds(): string[] {
return [...V02_SERVICE_IDS];
}
export const G14_CI_TOOLS_IMAGE_REPO = "127.0.0.1:5000/hwlab/hwlab-ci-node-tools";
export const G14_CI_TOOLS_BASE_TAG = "node22-alpine-v1";
export const DEFAULT_INTERVAL_SECONDS = 600;
export const DEFAULT_MAX_CYCLES = 0;
export const DEFAULT_TIMEOUT_SECONDS = 1800;
export const G14_BRIEF_INDEX_ISSUE = 7;
export const BEIJING_OFFSET_MS = 8 * 60 * 60 * 1000;
export const V02_BUILD_TASKRUN_WARNING_SECONDS = 120;
export const V02_BUILD_TASKRUN_CRITICAL_SECONDS = 180;
export const V02_CONTROL_PLANE_REFRESH_TTL_MS = 5 * 60 * 1000;
export const V02_CONTROL_PLANE_REFRESH_LOCK_STALE_MS = 5 * 60 * 1000;
export const V02_GIT_MIRROR_PRESYNC_MAX_WAIT_MS = 120 * 1000;
export const V02_GIT_MIRROR_PRESYNC_LOCK_STALE_MS = 5 * 60 * 1000;
export const V02_GIT_MIRROR_PRESYNC_POLL_MS = 3 * 1000;
export type G14MonitorLane = "g14" | HwlabRuntimeLane;
export interface G14MonitorOptions {
lane: G14MonitorLane;
intervalSeconds: number;
maxCycles: number;
once: boolean;
dryRun: boolean;
worker: boolean;
timeoutSeconds: number;
}
export interface G14RecordRolloutOptions {
prNumber: number;
sourceCommit?: string;
pipelineRun?: string;
gitopsRevision?: string;
mergedAt?: string;
pipelineSucceededAt?: string;
finishedAt?: string;
dryRun: boolean;
}
export interface G14ControlPlaneOptions {
action: "status" | "closeout" | "apply" | "trigger-current" | "refresh" | "cleanup-runs" | "cleanup-released-pvs" | "runtime-migration";
lane: HwlabRuntimeLane | "g14" | "all";
dryRun: boolean;
confirm: boolean;
wait: boolean;
rerun: boolean;
allowLiveDbRead: boolean;
timeoutSeconds: number;
minAgeMinutes: number;
limit: number;
sourceCommit?: string;
pipelineRun?: string;
history: boolean;
}
export interface G14LegacyRetirementOptions {
action: "status" | "plan" | "execute";
dryRun: boolean;
confirm: boolean;
wait: boolean;
timeoutSeconds: number;
reason: string;
}
export type V02StatusTargetMode = "latest-source-head" | "source-commit" | "pipeline-run";
export interface V02ControlPlaneStatusTarget {
sourceCommit?: string | null;
pipelineRun?: string | null;
mode?: V02StatusTargetMode;
includeHistory?: boolean;
}
export interface G14ToolsImageOptions {
action: "status" | "build";
name: "ci-node-tools";
tag: string;
dockerfile: string;
dryRun: boolean;
confirm: boolean;
timeoutSeconds: number;
}
export interface G14UpstreamImageOptions {
action: "status" | "ensure";
name: "openfga";
tag: string;
dryRun: boolean;
confirm: boolean;
timeoutSeconds: number;
}
export interface G14GitMirrorOptions {
action: "status" | "apply" | "sync" | "flush";
lane: HwlabRuntimeLane;
dryRun: boolean;
confirm: boolean;
wait: boolean;
timeoutSeconds: number;
}
export interface G14ObservabilityOptions {
action: "status" | "apply" | "query" | "targets" | "boundary" | "closeout";
lane: "v02";
dryRun: boolean;
confirm: boolean;
wait: boolean;
timeoutSeconds: number;
query: string;
expectCount?: number;
expectValue?: string;
}
export interface G14SecretOptions {
action: "status" | "ensure" | "delete";
lane: HwlabRuntimeLane;
dryRun: boolean;
confirm: boolean;
name: string;
key?: string;
preset: "openfga" | "master-server-admin-api-key" | "generic-delete";
timeoutSeconds: number;
}
export interface CommandJsonResult {
ok: boolean;
command: string[];
exitCode: number | null;
stdout: string;
stderr: string;
parsed: unknown | null;
durationMs?: number;
timedOut?: boolean;
stdoutBytes?: number;
stderrBytes?: number;
}
export interface RemoteAsyncCommandSpec {
script: string;
timeoutSeconds: number;
label: string;
token: string;
command: string[];
}
export interface ShellSection {
stdout: string;
exitCode: number | null;
}
export interface OpenPullRequest {
number: number;
title?: string;
url?: string;
baseRefName?: string;
headRefName?: string;
mergeable?: string | null;
mergeStateStatus?: string | null;
draft?: boolean;
}
export interface CiServiceMetric {
taskRun: string;
serviceId: string;
status: string;
durationSeconds: number | null;
buildBackend: string | null;
reusedFrom: string | null;
imageTag: string | null;
sourceCommitId: string | null;
}
export interface CiPipelineMetrics {
ok: boolean;
pipelineRun: string;
serviceTotal: number;
reusedCount: number;
rebuildCount: number;
reusedServices: string[];
rebuildServices: string[];
services: CiServiceMetric[];
degradedReason?: string;
rawText?: string;
}
export interface V02PrCommentInput {
pr: OpenPullRequest;
phase: string;
state: "waiting-ci" | "blocked" | "merge-failed" | "cd-started" | "cd-succeeded" | "cd-superseded" | "cd-failed" | "cd-timeout" | "cd-blocked";
startedAt: string;
observedAt: string;
elapsedSeconds: number | null;
preflight?: Record<string, unknown>;
merge?: Record<string, unknown>;
sourceCommit?: string | null;
pipelineRun?: string | null;
cd?: Record<string, unknown>;
flush?: Record<string, unknown>;
dryRun?: boolean;
message?: string;
}
+2
View File
@@ -0,0 +1,2 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. Thin compatibility entry for scripts/src/hwlab-legacy-runtime.ts.
export * from "./hwlab-legacy-runtime/index";
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. cd-trigger module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. cd-trigger module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:9508-10162 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:9508-10162 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -10,15 +10,15 @@ import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import type { CommandJsonResult, G14MonitorOptions, OpenPullRequest } from "./types";
import { runRuntimeLaneControlPlane, runV02ControlPlane, runtimeLaneControlPlaneStatus, v02ControlPlaneStatus } from "./control-plane";
import { runG14GitMirror, runGitMirrorStatus } from "./git-mirror";
import { activeV02PipelineRuns, commentRuntimeLanePullRequest, commentV02PullRequest, durationSeconds, reportRuntimeLaneAutomationIssue, runtimeLaneCdFailed, runtimeLaneCdPassed, sourceCommitFromMergeResult, summarizeRuntimeLaneCdStatus, summarizeV02CdStatus, v02CdFailed, v02CdPassed, v02PipelineSucceeded, waitForV02Head } from "./pr-monitor";
import { commandData, compactCommandResult, isCommandSuccess, nested, printEvent, printRuntimeLanePrMonitorProgress, printV02PrMonitorProgress, record, sleep, stringOrNull } from "./remote";
import { resolveRuntimeLaneHead, runtimeLanePipelineRunName, v02PipelineRunName } from "./source";
import type { CommandJsonResult, LegacyRuntimeMonitorOptions, OpenPullRequest } from "./types";
import { runRuntimeLaneControlPlane, runDefaultRuntimeLaneControlPlane, runtimeLaneControlPlaneStatus, defaultRuntimeLaneControlPlaneStatus } from "./control-plane";
import { runLegacyRuntimeGitMirror, runGitMirrorStatus } from "./git-mirror";
import { activeDefaultRuntimeLanePipelineRuns, commentRuntimeLanePullRequest, commentDefaultRuntimeLanePullRequest, durationSeconds, reportRuntimeLaneAutomationIssue, runtimeLaneCdFailed, runtimeLaneCdPassed, sourceCommitFromMergeResult, summarizeRuntimeLaneCdStatus, summarizeDefaultRuntimeLaneCdStatus, defaultRuntimeLaneCdFailed, defaultRuntimeLaneCdPassed, defaultRuntimeLanePipelineSucceeded, waitForDefaultRuntimeLaneHead } from "./pr-monitor";
import { commandData, compactCommandResult, isCommandSuccess, nested, printEvent, printRuntimeLanePrMonitorProgress, printDefaultRuntimeLanePrMonitorProgress, record, sleep, stringOrNull } from "./remote";
import { resolveRuntimeLaneHead, runtimeLanePipelineRunName, defaultRuntimeLanePipelineRunName } from "./source";
export function triggerV02Current(timeoutSeconds: number): Record<string, unknown> {
return runV02ControlPlane({
export function triggerDefaultRuntimeLaneCurrent(timeoutSeconds: number): Record<string, unknown> {
return runDefaultRuntimeLaneControlPlane({
action: "trigger-current",
lane: "v02",
dryRun: false,
@@ -31,10 +31,10 @@ export function triggerV02Current(timeoutSeconds: number): Record<string, unknow
});
}
export function flushV02GitMirrorIfNeeded(status: Record<string, unknown>, timeoutSeconds: number): Record<string, unknown> {
export function flushDefaultRuntimeLaneGitMirrorIfNeeded(status: Record<string, unknown>, timeoutSeconds: number): Record<string, unknown> {
const pendingFlush = nested(status, ["gitMirror", "summary", "pendingFlush"]);
if (pendingFlush !== true) return { ok: true, skipped: true, reason: "git-mirror-already-flushed" };
return runG14GitMirror({
return runLegacyRuntimeGitMirror({
action: "flush",
lane: "v02",
confirm: true,
@@ -81,7 +81,7 @@ export function runtimeLaneCdPassedWithMirror(spec: HwlabRuntimeLaneSpec, status
export function flushRuntimeLaneGitMirrorIfNeeded(spec: HwlabRuntimeLaneSpec, status: Record<string, unknown>, timeoutSeconds: number): Record<string, unknown> {
if (runtimeLaneGitopsSynced(spec, status)) return { ok: true, skipped: true, reason: `${spec.lane}-git-mirror-already-flushed` };
return runG14GitMirror({
return runLegacyRuntimeGitMirror({
action: "flush",
lane: spec.lane,
confirm: true,
@@ -121,19 +121,19 @@ export async function waitForRuntimeLaneHead(spec: HwlabRuntimeLaneSpec, expecte
return { ok: false, sourceCommit: head, expectedCommit, matched: false, waitedSeconds: Math.round((Date.now() - started) / 1000), degradedReason: `${spec.lane}-source-head-not-aligned-after-merge` };
}
export async function waitForV02Cd(sourceCommit: string, timeoutSeconds: number): Promise<Record<string, unknown>> {
export async function waitForDefaultRuntimeLaneCd(sourceCommit: string, timeoutSeconds: number): Promise<Record<string, unknown>> {
const started = Date.now();
const startedAt = new Date(started).toISOString();
const pipelineRun = v02PipelineRunName(sourceCommit);
const pipelineRun = defaultRuntimeLanePipelineRunName(sourceCommit);
let lastStatus: Record<string, unknown> = {};
let firstPassedAt: string | null = null;
while (Date.now() - started < timeoutSeconds * 1000) {
lastStatus = v02ControlPlaneStatus({ sourceCommit, mode: "source-commit" });
const summary = summarizeV02CdStatus(lastStatus);
lastStatus = defaultRuntimeLaneControlPlaneStatus({ sourceCommit, mode: "source-commit" });
const summary = summarizeDefaultRuntimeLaneCdStatus(lastStatus);
printEvent("v02.cd.status", { sourceCommit, pipelineRun, summary });
printV02PrMonitorProgress({ stage: "cd-status", status: "running", sourceCommit, pipelineRun, targetValidationState: summary.targetValidationState ?? null, pipelineStatus: summary.pipelineStatus ?? null, pendingFlush: summary.pendingFlush ?? null });
if (v02PipelineSucceeded(lastStatus) && summary.pendingFlush === true) {
const flush = flushV02GitMirrorIfNeeded(lastStatus, Math.min(timeoutSeconds, 600));
printDefaultRuntimeLanePrMonitorProgress({ stage: "cd-status", status: "running", sourceCommit, pipelineRun, targetValidationState: summary.targetValidationState ?? null, pipelineStatus: summary.pipelineStatus ?? null, pendingFlush: summary.pendingFlush ?? null });
if (defaultRuntimeLanePipelineSucceeded(lastStatus) && summary.pendingFlush === true) {
const flush = flushDefaultRuntimeLaneGitMirrorIfNeeded(lastStatus, Math.min(timeoutSeconds, 600));
if (record(flush).ok !== true) {
return {
ok: false,
@@ -147,10 +147,10 @@ export async function waitForV02Cd(sourceCommit: string, timeoutSeconds: number)
flush,
};
}
const finalStatus = v02ControlPlaneStatus({ sourceCommit, mode: "source-commit" });
const finalSummary = summarizeV02CdStatus(finalStatus);
if (v02CdPassed(finalStatus)) {
printV02PrMonitorProgress({ stage: "git-mirror-flush", status: "succeeded", sourceCommit, pipelineRun, pendingFlush: finalSummary.pendingFlush ?? null });
const finalStatus = defaultRuntimeLaneControlPlaneStatus({ sourceCommit, mode: "source-commit" });
const finalSummary = summarizeDefaultRuntimeLaneCdStatus(finalStatus);
if (defaultRuntimeLaneCdPassed(finalStatus)) {
printDefaultRuntimeLanePrMonitorProgress({ stage: "git-mirror-flush", status: "succeeded", sourceCommit, pipelineRun, pendingFlush: finalSummary.pendingFlush ?? null });
return {
ok: true,
phase: "cd-passed",
@@ -165,27 +165,27 @@ export async function waitForV02Cd(sourceCommit: string, timeoutSeconds: number)
}
lastStatus = finalStatus;
printEvent("v02.cd.after-flush", { sourceCommit, pipelineRun, flushOk: record(flush).ok, summary: finalSummary });
printV02PrMonitorProgress({ stage: "git-mirror-flush", status: record(flush).ok === true ? "succeeded" : "failed", sourceCommit, pipelineRun, pendingFlush: finalSummary.pendingFlush ?? null });
printDefaultRuntimeLanePrMonitorProgress({ stage: "git-mirror-flush", status: record(flush).ok === true ? "succeeded" : "failed", sourceCommit, pipelineRun, pendingFlush: finalSummary.pendingFlush ?? null });
}
if (v02CdPassed(lastStatus)) {
if (defaultRuntimeLaneCdPassed(lastStatus)) {
firstPassedAt ??= new Date().toISOString();
const flush = flushV02GitMirrorIfNeeded(lastStatus, Math.min(timeoutSeconds, 600));
const finalStatus = v02ControlPlaneStatus({ sourceCommit, mode: "source-commit" });
printV02PrMonitorProgress({ stage: "cd-status", status: v02CdPassed(finalStatus) ? "succeeded" : "failed", sourceCommit, pipelineRun, targetValidationState: nested(finalStatus, ["targetValidation", "state"]) ?? null });
const flush = flushDefaultRuntimeLaneGitMirrorIfNeeded(lastStatus, Math.min(timeoutSeconds, 600));
const finalStatus = defaultRuntimeLaneControlPlaneStatus({ sourceCommit, mode: "source-commit" });
printDefaultRuntimeLanePrMonitorProgress({ stage: "cd-status", status: defaultRuntimeLaneCdPassed(finalStatus) ? "succeeded" : "failed", sourceCommit, pipelineRun, targetValidationState: nested(finalStatus, ["targetValidation", "state"]) ?? null });
return {
ok: v02CdPassed(finalStatus),
phase: v02CdPassed(finalStatus) ? "cd-passed" : "git-mirror-flush",
ok: defaultRuntimeLaneCdPassed(finalStatus),
phase: defaultRuntimeLaneCdPassed(finalStatus) ? "cd-passed" : "git-mirror-flush",
startedAt,
finishedAt: new Date().toISOString(),
sourceCommit,
pipelineRun,
status: summarizeV02CdStatus(finalStatus),
status: summarizeDefaultRuntimeLaneCdStatus(finalStatus),
rawStatus: finalStatus,
flush,
};
}
if (v02CdFailed(lastStatus)) {
printV02PrMonitorProgress({ stage: "cd-status", status: "failed", sourceCommit, pipelineRun, pipelineStatus: summary.pipelineStatus ?? null, pipelineReason: summary.pipelineReason ?? null });
if (defaultRuntimeLaneCdFailed(lastStatus)) {
printDefaultRuntimeLanePrMonitorProgress({ stage: "cd-status", status: "failed", sourceCommit, pipelineRun, pipelineStatus: summary.pipelineStatus ?? null, pipelineReason: summary.pipelineReason ?? null });
return {
ok: false,
phase: "cd-failed",
@@ -208,7 +208,7 @@ export async function waitForV02Cd(sourceCommit: string, timeoutSeconds: number)
pipelineRun,
timeoutSeconds,
firstPassedAt,
status: summarizeV02CdStatus(lastStatus),
status: summarizeDefaultRuntimeLaneCdStatus(lastStatus),
rawStatus: lastStatus,
};
}
@@ -302,11 +302,11 @@ export async function waitForRuntimeLaneCd(spec: HwlabRuntimeLaneSpec, sourceCom
};
}
export async function runV02PrAutoCd(pr: OpenPullRequest, preflight: Record<string, unknown>, merge: CommandJsonResult, options: G14MonitorOptions, startedAt: string): Promise<Record<string, unknown>> {
export async function runDefaultRuntimeLanePrAutoCd(pr: OpenPullRequest, preflight: Record<string, unknown>, merge: CommandJsonResult, options: LegacyRuntimeMonitorOptions, startedAt: string): Promise<Record<string, unknown>> {
const mergeRaceState = isCommandSuccess(merge) ? null : mergeCommandRaceState(merge);
if (!isCommandSuccess(merge) && mergeRaceState !== "merged") {
printV02PrMonitorProgress({ stage: "merge", status: "failed", pr: pr.number, mergeRaceState });
const comment = commentV02PullRequest({
printDefaultRuntimeLanePrMonitorProgress({ stage: "merge", status: "failed", pr: pr.number, mergeRaceState });
const comment = commentDefaultRuntimeLanePullRequest({
pr,
phase: "merge",
state: "merge-failed",
@@ -324,8 +324,8 @@ export async function runV02PrAutoCd(pr: OpenPullRequest, preflight: Record<stri
}
const expectedCommit = options.dryRun ? null : sourceCommitFromMergeResult(merge, pr.number);
if (!options.dryRun && expectedCommit === null) {
printV02PrMonitorProgress({ stage: "source-head", status: "failed", pr: pr.number, sourceCommit: null, pipelineRun: null, degradedReason: "merge-commit-unresolved" });
const comment = commentV02PullRequest({
printDefaultRuntimeLanePrMonitorProgress({ stage: "source-head", status: "failed", pr: pr.number, sourceCommit: null, pipelineRun: null, degradedReason: "merge-commit-unresolved" });
const comment = commentDefaultRuntimeLanePullRequest({
pr,
phase: "merge-commit",
state: "cd-failed",
@@ -341,11 +341,11 @@ export async function runV02PrAutoCd(pr: OpenPullRequest, preflight: Record<stri
});
return { ok: false, phase: "merge-commit", pr, merge: commandData(merge), comment, degradedReason: "merge-commit-unresolved" };
}
const headWait = options.dryRun ? { ok: true, sourceCommit: null, expectedCommit, matched: true } : await waitForV02Head(expectedCommit, 120);
const headWait = options.dryRun ? { ok: true, sourceCommit: null, expectedCommit, matched: true } : await waitForDefaultRuntimeLaneHead(expectedCommit, 120);
const sourceCommit = typeof record(headWait).sourceCommit === "string" ? String(record(headWait).sourceCommit) : null;
const pipelineRun = sourceCommit === null ? null : v02PipelineRunName(sourceCommit);
printV02PrMonitorProgress({ stage: "merge", status: "succeeded", pr: pr.number, sourceCommit, pipelineRun, mergeRaceState });
const startedComment = commentV02PullRequest({
const pipelineRun = sourceCommit === null ? null : defaultRuntimeLanePipelineRunName(sourceCommit);
printDefaultRuntimeLanePrMonitorProgress({ stage: "merge", status: "succeeded", pr: pr.number, sourceCommit, pipelineRun, mergeRaceState });
const startedComment = commentDefaultRuntimeLanePullRequest({
pr,
phase: options.dryRun ? "dry-run" : "cd-trigger",
state: "cd-started",
@@ -368,8 +368,8 @@ export async function runV02PrAutoCd(pr: OpenPullRequest, preflight: Record<stri
}
if (options.dryRun) return { ok: true, action: "dry-run-merge", pr, merge: commandData(merge), comment: startedComment };
if (sourceCommit === null || record(headWait).ok !== true) {
printV02PrMonitorProgress({ stage: "source-head", status: "failed", pr: pr.number, sourceCommit, pipelineRun, expectedCommit });
const comment = commentV02PullRequest({
printDefaultRuntimeLanePrMonitorProgress({ stage: "source-head", status: "failed", pr: pr.number, sourceCommit, pipelineRun, expectedCommit });
const comment = commentDefaultRuntimeLanePullRequest({
pr,
phase: "v02-source-head",
state: "cd-timeout",
@@ -385,17 +385,17 @@ export async function runV02PrAutoCd(pr: OpenPullRequest, preflight: Record<stri
});
return { ok: false, phase: "v02-head", pr, merge: commandData(merge), expectedCommit, headWait, comment: record(comment).ok === true ? comment : startedComment, degradedReason: "v02-head-unresolved-after-merge" };
}
const before = v02ControlPlaneStatus({ sourceCommit, mode: "source-commit" });
const beforeSummary = summarizeV02CdStatus(before);
const activeRuns = activeV02PipelineRuns(before);
const before = defaultRuntimeLaneControlPlaneStatus({ sourceCommit, mode: "source-commit" });
const beforeSummary = summarizeDefaultRuntimeLaneCdStatus(before);
const activeRuns = activeDefaultRuntimeLanePipelineRuns(before);
if (activeRuns.length > 0) {
printEvent("v02.cd.active-runs-observed", { pr: pr.number, sourceCommit, pipelineRun, activeCount: activeRuns.length, activeRuns: activeRuns.slice(0, 5), latestOnlyPolicy: "do-not-wait-or-cancel-old-runs" });
}
const trigger = v02CdPassed(before) ? { ok: true, skipped: true, reason: "source-commit-already-deployed" } : triggerV02Current(Math.min(options.timeoutSeconds, 600));
const trigger = defaultRuntimeLaneCdPassed(before) ? { ok: true, skipped: true, reason: "source-commit-already-deployed" } : triggerDefaultRuntimeLaneCurrent(Math.min(options.timeoutSeconds, 600));
printEvent("v02.cd.trigger", { pr: pr.number, sourceCommit, pipelineRun, ok: record(trigger).ok, skipped: record(trigger).skipped ?? false, degradedReason: record(trigger).degradedReason ?? null });
printV02PrMonitorProgress({ stage: "cd-trigger", status: record(trigger).ok === true ? "succeeded" : "failed", pr: pr.number, sourceCommit, pipelineRun, activeCount: activeRuns.length, skipped: record(trigger).skipped ?? false, degradedReason: record(trigger).degradedReason ?? null });
printDefaultRuntimeLanePrMonitorProgress({ stage: "cd-trigger", status: record(trigger).ok === true ? "succeeded" : "failed", pr: pr.number, sourceCommit, pipelineRun, activeCount: activeRuns.length, skipped: record(trigger).skipped ?? false, degradedReason: record(trigger).degradedReason ?? null });
if (record(trigger).ok !== true) {
const comment = commentV02PullRequest({
const comment = commentDefaultRuntimeLanePullRequest({
pr,
phase: "cd-trigger",
state: "cd-failed",
@@ -412,13 +412,13 @@ export async function runV02PrAutoCd(pr: OpenPullRequest, preflight: Record<stri
});
return { ok: false, phase: "cd-trigger", pr, sourceCommit, pipelineRun, trigger, comment };
}
const cd = await waitForV02Cd(sourceCommit, options.timeoutSeconds);
const cd = await waitForDefaultRuntimeLaneCd(sourceCommit, options.timeoutSeconds);
const cdStatus = record(cd.status);
const flush = record(cd.flush);
const cdOk = cd.ok === true;
const cdPhase = String(cd.phase ?? "");
const cdSuperseded = cdOk && cdStatus.targetValidationState === "superseded";
const finalComment = commentV02PullRequest({
const finalComment = commentDefaultRuntimeLanePullRequest({
pr,
phase: cdPhase,
state: cdOk ? (cdSuperseded ? "cd-superseded" : "cd-succeeded") : cdPhase === "timeout" ? "cd-timeout" : "cd-failed",
@@ -453,7 +453,7 @@ export async function runV02PrAutoCd(pr: OpenPullRequest, preflight: Record<stri
};
}
export async function runRuntimeLanePrAutoCd(spec: HwlabRuntimeLaneSpec, pr: OpenPullRequest, preflight: Record<string, unknown>, merge: CommandJsonResult, options: G14MonitorOptions, startedAt: string): Promise<Record<string, unknown>> {
export async function runRuntimeLanePrAutoCd(spec: HwlabRuntimeLaneSpec, pr: OpenPullRequest, preflight: Record<string, unknown>, merge: CommandJsonResult, options: LegacyRuntimeMonitorOptions, startedAt: string): Promise<Record<string, unknown>> {
const mergeRaceState = isCommandSuccess(merge) ? null : mergeCommandRaceState(merge);
if (!isCommandSuccess(merge) && mergeRaceState !== "merged") {
printRuntimeLanePrMonitorProgress(spec, { stage: "merge", status: "failed", pr: pr.number, mergeRaceState });
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. cleanup module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. cleanup module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:2788-3345 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:2788-3345 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -10,11 +10,11 @@ import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import type { CommandJsonResult, G14ControlPlaneOptions } from "./types";
import { g14HostScript } from "./images";
import type { CommandJsonResult, LegacyRuntimeControlPlaneOptions } from "./types";
import { legacyRuntimeHostScript } from "./images";
import { statusText } from "./pr-monitor";
import { g14K3s, isCommandSuccess, nested, shellQuote, shortSha, stringOrNull } from "./remote";
import { runtimeLanePipelineRunName, v02PipelineRunName } from "./source";
import { legacyRuntimeK3s, isCommandSuccess, nested, shellQuote, shortSha, stringOrNull } from "./remote";
import { runtimeLanePipelineRunName, defaultRuntimeLanePipelineRunName } from "./source";
import { CI_NAMESPACE } from "./types";
export interface CleanupPipelineRunRow {
@@ -25,7 +25,7 @@ export interface CleanupPipelineRunRow {
reason: string | null;
}
export function pipelinePrefixesForLane(lane: G14ControlPlaneOptions["lane"]): string[] {
export function pipelinePrefixesForLane(lane: LegacyRuntimeControlPlaneOptions["lane"]): string[] {
if (isHwlabRuntimeLane(lane)) return [`${hwlabRuntimeLaneSpec(lane).pipelineRunPrefix}-`];
if (lane === "g14") return ["hwlab-g14-ci-poll-"];
return [...hwlabRuntimeLaneIds().map((runtimeLane) => `${hwlabRuntimeLaneSpec(runtimeLane).pipelineRunPrefix}-`), "hwlab-g14-ci-poll-"];
@@ -100,7 +100,7 @@ export function remoteStoragePathEstimates(paths: Array<string | null | undefine
" printf '%s\\t%s\\n' \"$path\" \"$bytes\"",
"done",
].join("\n");
const result = g14HostScript(script, 120_000);
const result = legacyRuntimeHostScript(script, 120_000);
if (!isCommandSuccess(result)) return estimates;
for (const line of statusText(result).split(/\r?\n/u)) {
const [path = "", rawBytes = ""] = line.trim().split("\t");
@@ -191,7 +191,7 @@ export function cleanupPipelineRunTargetCandidateFromTextForTest(input: {
});
}
export function listCleanupPipelineRuns(options: G14ControlPlaneOptions): Record<string, unknown>[] {
export function listCleanupPipelineRuns(options: LegacyRuntimeControlPlaneOptions): Record<string, unknown>[] {
if (options.lane !== "g14" && options.lane !== "all" && !isHwlabRuntimeLane(options.lane)) {
throw new Error("control-plane cleanup-runs requires --lane v02|v03|g14|all");
}
@@ -199,10 +199,10 @@ export function listCleanupPipelineRuns(options: G14ControlPlaneOptions): Record
? undefined
: isHwlabRuntimeLane(options.lane)
? runtimeLanePipelineRunName(hwlabRuntimeLaneSpec(options.lane), options.sourceCommit)
: v02PipelineRunName(options.sourceCommit));
: defaultRuntimeLanePipelineRunName(options.sourceCommit));
const now = Date.now();
if (targetPipelineRun !== undefined) {
const targetResult = g14K3s([
const targetResult = legacyRuntimeK3s([
"kubectl",
"get",
"pipelinerun",
@@ -222,7 +222,7 @@ export function listCleanupPipelineRuns(options: G14ControlPlaneOptions): Record
nowMs: now,
})];
}
const result = g14K3s([
const result = legacyRuntimeK3s([
"kubectl",
"get",
"pipelinerun",
@@ -267,7 +267,7 @@ export function listOwnedWorkspacePvcs(pipelineRunNames: string[]): Record<strin
if (pipelineRunNames.length === 0) {
return [];
}
const result = g14K3s([
const result = legacyRuntimeK3s([
"kubectl",
"get",
"pvc",
@@ -279,7 +279,7 @@ export function listOwnedWorkspacePvcs(pipelineRunNames: string[]): Record<strin
if (!isCommandSuccess(result)) {
throw new Error(`failed to list hwlab-ci PipelineRun PVCs: ${commandErrorSummary(result)}`);
}
const pvResult = g14K3s([
const pvResult = legacyRuntimeK3s([
"kubectl",
"get",
"pv",
@@ -289,7 +289,7 @@ export function listOwnedWorkspacePvcs(pipelineRunNames: string[]): Record<strin
if (!isCommandSuccess(pvResult)) {
throw new Error(`failed to list hwlab-ci PipelineRun PV backing paths: ${commandErrorSummary(pvResult)}`);
}
const podResult = g14K3s([
const podResult = legacyRuntimeK3s([
"kubectl",
"get",
"pod",
@@ -361,11 +361,11 @@ export function deletePipelineRuns(names: string[], timeoutMs: number): CommandJ
parsed: null,
};
}
return g14K3s(["kubectl", "delete", "pipelinerun", "-n", CI_NAMESPACE, ...names, "--ignore-not-found=true"], timeoutMs);
return legacyRuntimeK3s(["kubectl", "delete", "pipelinerun", "-n", CI_NAMESPACE, ...names, "--ignore-not-found=true"], timeoutMs);
}
export function listReleasedCiWorkspacePvs(options: G14ControlPlaneOptions): Record<string, unknown>[] {
const result = g14K3s([
export function listReleasedCiWorkspacePvs(options: LegacyRuntimeControlPlaneOptions): Record<string, unknown>[] {
const result = legacyRuntimeK3s([
"kubectl",
"get",
"pv",
@@ -416,10 +416,10 @@ export function deletePersistentVolumes(names: string[], timeoutMs: number): Com
parsed: null,
};
}
return g14K3s(["kubectl", "delete", "pv", ...names, "--ignore-not-found=true"], timeoutMs);
return legacyRuntimeK3s(["kubectl", "delete", "pv", ...names, "--ignore-not-found=true"], timeoutMs);
}
export function runControlPlaneReleasedPvCleanup(options: G14ControlPlaneOptions): Record<string, unknown> {
export function runControlPlaneReleasedPvCleanup(options: LegacyRuntimeControlPlaneOptions): Record<string, unknown> {
const candidates = listReleasedCiWorkspacePvs(options);
const candidateNames = candidates.map((item) => String(item.name));
const estimatedReclaimBytes = sumEstimatedBytes(candidates);
@@ -460,7 +460,7 @@ export function runControlPlaneReleasedPvCleanup(options: G14ControlPlaneOptions
};
}
export function runV02RuntimeMigration(options: G14ControlPlaneOptions, sourceCommit: string): Record<string, unknown> {
export function runDefaultRuntimeLaneRuntimeMigration(options: LegacyRuntimeControlPlaneOptions, sourceCommit: string): Record<string, unknown> {
const reportPath = `/tmp/hwlab-v02-runtime-migration-${shortSha(sourceCommit)}.json`;
const migrationArgs = options.dryRun
? [
@@ -487,7 +487,7 @@ export function runV02RuntimeMigration(options: G14ControlPlaneOptions, sourceCo
"cmd/hwlab-cloud-api/migrate.ts",
...migrationArgs,
];
const result = g14K3s(["kubectl", ...command], options.timeoutSeconds * 1000);
const result = legacyRuntimeK3s(["kubectl", ...command], options.timeoutSeconds * 1000);
const ok = isCommandSuccess(result);
return {
ok,
@@ -513,7 +513,7 @@ export function runV02RuntimeMigration(options: G14ControlPlaneOptions, sourceCo
};
}
export function runControlPlaneCleanup(options: G14ControlPlaneOptions): Record<string, unknown> {
export function runControlPlaneCleanup(options: LegacyRuntimeControlPlaneOptions): Record<string, unknown> {
const candidates = listCleanupPipelineRuns(options);
const candidateNames = candidates
.filter((item) => item.selected !== false)
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. control-plane module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. control-plane module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:3737-5054 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:3737-5054 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -10,16 +10,16 @@ import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import type { CommandJsonResult, G14ControlPlaneOptions, V02ControlPlaneStatusTarget, V02StatusTargetMode } from "./types";
import type { CommandJsonResult, LegacyRuntimeControlPlaneOptions, DefaultRuntimeLaneControlPlaneStatusTarget, DefaultRuntimeLaneStatusTargetMode } from "./types";
import { runtimeLaneCdPassedWithMirror, runtimeLaneStatusWithGitMirror } from "./cd-trigger";
import { runControlPlaneCleanup, runControlPlaneReleasedPvCleanup, runV02RuntimeMigration } from "./cleanup";
import { compactGitMirrorSync, gitMirrorStatusSummary, preSyncV02GitMirror, runGitMirrorStatus, runGitMirrorSync, runtimeLaneGitMirrorSourceInSync } from "./git-mirror";
import { statusText, summarizeRuntimeLaneCdStatus, v02ControlPlaneCloseout } from "./pr-monitor";
import { compactCommandResult, compactControlPlaneRefresh, compactTriggerCommandResult, g14K3s, isCommandSuccess, nested, printProgressEvent, printRuntimeLaneTriggerProgress, record, shellQuote, stringOrNull } from "./remote";
import { applyRuntimeLaneControlPlaneFiles, applyV02ControlPlaneFiles, cleanupRuntimeLaneRenderDir, cleanupV02RenderDir, deleteV02ObsoleteCronJobs, refreshV02ControlPlaneBeforeTrigger, runRuntimeLaneRenderToTemp, runV02RenderToTemp } from "./render";
import { resolveRuntimeLaneHead, resolveRuntimeLaneLatestRemoteHead, resolveV02Head, resolveV02LatestRemoteHead, runtimeLanePipelineRunName, runtimeLaneRerunPipelineRunName, v02PipelineRunName } from "./source";
import { ARGO_NAMESPACE, CI_NAMESPACE, V02_APP, V02_CICD_REPO, V02_LANE_SPEC, V02_PIPELINE, V02_SOURCE_BRANCH, V02_WORKSPACE } from "./types";
import { getPipelineRunCompact, getV02TriggerSnapshot, keyValueLinesFromText, listV02PipelineRunsCompactFromText, numericField, parsePipelineRunRows, parseShellSections, pipelineRunCompactFromText, pipelineRunRowsJsonPath, runtimeLaneTaskRunDiagnosticsFromText, runtimeLaneTaskRunDiagnosticsScript, shellSectionOk, taskRunsCompactFromText, timestampMs, v02CloseoutVerdict, v02CommitAlignment, v02ControlPlaneStatusBundle, v02ExistingPipelineRunReuseDecision, v02FalseGreenGuard, v02LatestOnlyTargetValidation, v02PlanArtifactsFromText, v02RuntimeWorkloadsFromText, v02SourceHeadsFromText, v02TargetValidation, v02WebAssetsFromText } from "./v02-status";
import { runControlPlaneCleanup, runControlPlaneReleasedPvCleanup, runDefaultRuntimeLaneRuntimeMigration } from "./cleanup";
import { compactGitMirrorSync, gitMirrorStatusSummary, preSyncDefaultRuntimeLaneGitMirror, runGitMirrorStatus, runGitMirrorSync, runtimeLaneGitMirrorSourceInSync } from "./git-mirror";
import { statusText, summarizeRuntimeLaneCdStatus, defaultRuntimeLaneControlPlaneCloseout } from "./pr-monitor";
import { compactCommandResult, compactControlPlaneRefresh, compactTriggerCommandResult, legacyRuntimeK3s, isCommandSuccess, nested, printProgressEvent, printRuntimeLaneTriggerProgress, record, shellQuote, stringOrNull } from "./remote";
import { applyRuntimeLaneControlPlaneFiles, applyDefaultRuntimeLaneControlPlaneFiles, cleanupRuntimeLaneRenderDir, cleanupDefaultRuntimeLaneRenderDir, deleteDefaultRuntimeLaneObsoleteCronJobs, refreshDefaultRuntimeLaneControlPlaneBeforeTrigger, runRuntimeLaneRenderToTemp, runDefaultRuntimeLaneRenderToTemp } from "./render";
import { resolveRuntimeLaneHead, resolveRuntimeLaneLatestRemoteHead, resolveDefaultRuntimeLaneHead, resolveDefaultRuntimeLaneLatestRemoteHead, runtimeLanePipelineRunName, runtimeLaneRerunPipelineRunName, defaultRuntimeLanePipelineRunName } from "./source";
import { ARGO_NAMESPACE, CI_NAMESPACE, DEFAULT_RUNTIME_LANE_APP, DEFAULT_RUNTIME_LANE_CICD_REPO, DEFAULT_RUNTIME_LANE_LANE_SPEC, DEFAULT_RUNTIME_LANE_PIPELINE, DEFAULT_RUNTIME_LANE_SOURCE_BRANCH, DEFAULT_RUNTIME_LANE_WORKSPACE } from "./types";
import { getPipelineRunCompact, getDefaultRuntimeLaneTriggerSnapshot, keyValueLinesFromText, listDefaultRuntimeLanePipelineRunsCompactFromText, numericField, parsePipelineRunRows, parseShellSections, pipelineRunCompactFromText, pipelineRunRowsJsonPath, runtimeLaneTaskRunDiagnosticsFromText, runtimeLaneTaskRunDiagnosticsScript, shellSectionOk, taskRunsCompactFromText, timestampMs, defaultRuntimeLaneCloseoutVerdict, defaultRuntimeLaneCommitAlignment, defaultRuntimeLaneControlPlaneStatusBundle, defaultRuntimeLaneExistingPipelineRunReuseDecision, defaultRuntimeLaneFalseGreenGuard, defaultRuntimeLaneLatestOnlyTargetValidation, defaultRuntimeLanePlanArtifactsFromText, defaultRuntimeLaneRuntimeWorkloadsFromText, defaultRuntimeLaneSourceHeadsFromText, defaultRuntimeLaneTargetValidation, defaultRuntimeLaneWebAssetsFromText } from "./runtime-status";
export function runtimeLanePipelineRunManifest(spec: HwlabRuntimeLaneSpec, sourceCommit: string, pipelineRun = runtimeLanePipelineRunName(spec, sourceCommit)): Record<string, unknown> {
return {
@@ -76,8 +76,8 @@ export function runtimeLanePipelineRunManifest(spec: HwlabRuntimeLaneSpec, sourc
};
}
export function v02PipelineRunManifest(sourceCommit: string): Record<string, unknown> {
return runtimeLanePipelineRunManifest(V02_LANE_SPEC, sourceCommit);
export function defaultRuntimeLanePipelineRunManifest(sourceCommit: string): Record<string, unknown> {
return runtimeLanePipelineRunManifest(DEFAULT_RUNTIME_LANE_LANE_SPEC, sourceCommit);
}
export function createRuntimeLanePipelineRun(spec: HwlabRuntimeLaneSpec, sourceCommit: string, timeoutSeconds: number, pipelineRun = runtimeLanePipelineRunName(spec, sourceCommit)): CommandJsonResult {
@@ -100,22 +100,22 @@ export function createRuntimeLanePipelineRun(spec: HwlabRuntimeLaneSpec, sourceC
"fi",
`kubectl get pipelinerun -n ${shellQuote(CI_NAMESPACE)} ${shellQuote(pipelineRun)} -o jsonpath='{.metadata.name}{\"\\n\"}{.metadata.labels.hwlab\\.pikastech\\.local/source-commit}{\"\\n\"}{.status.conditions[0].status}{\"\\n\"}{.status.conditions[0].reason}{\"\\n\"}'`,
].join("\n");
return g14K3s(["sh", "--", script], timeoutSeconds * 1000);
return legacyRuntimeK3s(["sh", "--", script], timeoutSeconds * 1000);
}
export function createV02PipelineRun(sourceCommit: string, timeoutSeconds: number): CommandJsonResult {
return createRuntimeLanePipelineRun(V02_LANE_SPEC, sourceCommit, timeoutSeconds);
export function createDefaultRuntimeLanePipelineRun(sourceCommit: string, timeoutSeconds: number): CommandJsonResult {
return createRuntimeLanePipelineRun(DEFAULT_RUNTIME_LANE_LANE_SPEC, sourceCommit, timeoutSeconds);
}
export function v02ControlPlaneStatus(target: V02ControlPlaneStatusTarget = {}): Record<string, unknown> {
const targetMode: V02StatusTargetMode = target.mode
export function defaultRuntimeLaneControlPlaneStatus(target: DefaultRuntimeLaneControlPlaneStatusTarget = {}): Record<string, unknown> {
const targetMode: DefaultRuntimeLaneStatusTargetMode = target.mode
?? (target.pipelineRun !== undefined && target.pipelineRun !== null ? "pipeline-run" : target.sourceCommit !== undefined ? "source-commit" : "latest-source-head");
const includeHistory = target.includeHistory === true;
const strictHeadAlignment = targetMode === "latest-source-head";
const bundle = v02ControlPlaneStatusBundle({ ...target, mode: targetMode });
const bundle = defaultRuntimeLaneControlPlaneStatusBundle({ ...target, mode: targetMode });
const sections = parseShellSections(statusText(bundle));
const sourceCommit = stringOrNull(sections.sourceCommit?.stdout) ?? null;
const pipelineRun = stringOrNull(sections.pipelineRunName?.stdout) ?? (sourceCommit === null ? null : v02PipelineRunName(sourceCommit));
const pipelineRun = stringOrNull(sections.pipelineRunName?.stdout) ?? (sourceCommit === null ? null : defaultRuntimeLanePipelineRunName(sourceCommit));
const statusTargetFields = keyValueLinesFromText(sections.statusTarget?.stdout ?? "");
const queryNowMs = timestampMs(sections.queryNow?.stdout) ?? Date.now();
const sourceHeadsSection = sections.sourceHeads;
@@ -128,7 +128,7 @@ export function v02ControlPlaneStatus(target: V02ControlPlaneStatusTarget = {}):
const runtimeWorkloadsSection = sections.runtimeWorkloads;
const gitMirrorCacheSection = sections.gitMirrorCache;
const webAssetsSection = sections.webAssets;
const recentPipelineRuns = listV02PipelineRunsCompactFromText(
const recentPipelineRuns = listDefaultRuntimeLanePipelineRunsCompactFromText(
sections.recentPipelineRuns?.stdout ?? "",
shellSectionOk(sections.recentPipelineRuns),
"kubectl get pipelinerun -n hwlab-ci -l hwlab.pikastech.local/gitops-target=v02 -o jsonpath=<summary-fields>",
@@ -139,7 +139,7 @@ export function v02ControlPlaneStatus(target: V02ControlPlaneStatusTarget = {}):
);
const activePipelineRuns = Array.isArray(recentPipelineRuns.activeItems) ? recentPipelineRuns.activeItems : [];
const [targetRevision = "", path = "", syncRevision = "", syncStatus = "", health = ""] = String(argo?.stdout ?? "").split(/\r?\n/u);
const sourceHeads = v02SourceHeadsFromText(
const sourceHeads = defaultRuntimeLaneSourceHeadsFromText(
sourceHeadsSection?.stdout ?? "",
shellSectionOk(sourceHeadsSection),
sourceHeadsSection?.exitCode ?? null,
@@ -162,20 +162,20 @@ export function v02ControlPlaneStatus(target: V02ControlPlaneStatusTarget = {}):
taskRunsSection?.exitCode ?? null,
bundle.stderr,
);
const planArtifacts = v02PlanArtifactsFromText(
const planArtifacts = defaultRuntimeLanePlanArtifactsFromText(
planArtifactsSection?.stdout ?? "",
shellSectionOk(planArtifactsSection),
pipelineRun,
planArtifactsSection?.exitCode ?? null,
bundle.stderr,
);
const runtimeWorkloads = v02RuntimeWorkloadsFromText(
const runtimeWorkloads = defaultRuntimeLaneRuntimeWorkloadsFromText(
runtimeWorkloadsSection?.stdout ?? "",
shellSectionOk(runtimeWorkloadsSection),
runtimeWorkloadsSection?.exitCode ?? null,
bundle.stderr,
);
const webAssets = v02WebAssetsFromText(
const webAssets = defaultRuntimeLaneWebAssetsFromText(
webAssetsSection?.stdout ?? "",
shellSectionOk(webAssetsSection),
sourceCommit,
@@ -191,7 +191,7 @@ export function v02ControlPlaneStatus(target: V02ControlPlaneStatusTarget = {}):
exitCode: gitMirrorCacheSection?.exitCode ?? null,
stderr: shellSectionOk(gitMirrorCacheSection) ? "" : bundle.stderr.trim().slice(0, 2000),
};
const commitAlignment = v02CommitAlignment({
const commitAlignment = defaultRuntimeLaneCommitAlignment({
expectedSourceHead: sourceCommit,
sourceHeads,
gitMirrorSummary: record(gitMirror.summary),
@@ -201,7 +201,7 @@ export function v02ControlPlaneStatus(target: V02ControlPlaneStatusTarget = {}):
runtimeWorkloads,
webAssets,
});
const targetValidationBase = v02TargetValidation({
const targetValidationBase = defaultRuntimeLaneTargetValidation({
targetMode,
sourceCommit,
pipelineRun: pipelineRunInfo,
@@ -215,7 +215,7 @@ export function v02ControlPlaneStatus(target: V02ControlPlaneStatusTarget = {}):
gitMirror,
recentPipelineRuns,
});
const targetValidation = v02LatestOnlyTargetValidation({
const targetValidation = defaultRuntimeLaneLatestOnlyTargetValidation({
targetMode,
sourceCommit,
pipelineRun: pipelineRunInfo,
@@ -233,7 +233,7 @@ export function v02ControlPlaneStatus(target: V02ControlPlaneStatusTarget = {}):
newerSucceededRuns: targetValidation.newerSucceededRuns ?? [],
supersededRuntimeServices: targetValidation.supersededRuntimeServices ?? [],
}
: v02FalseGreenGuard({ sourceCommit, pipelineRun: pipelineRunInfo, taskRuns, planArtifacts, runtimeWorkloads });
: defaultRuntimeLaneFalseGreenGuard({ sourceCommit, pipelineRun: pipelineRunInfo, taskRuns, planArtifacts, runtimeWorkloads });
const baseOk = sourceCommit !== null && isCommandSuccess(bundle) && shellSectionOk(controlPlane) && shellSectionOk(argo);
const targetPipelineRunOk = strictHeadAlignment
? true
@@ -269,15 +269,15 @@ export function v02ControlPlaneStatus(target: V02ControlPlaneStatusTarget = {}):
sourceCommit,
sourceCommitSource,
expected: {
sourceRepo: V02_CICD_REPO,
workspace: V02_WORKSPACE,
sourceRepo: DEFAULT_RUNTIME_LANE_CICD_REPO,
workspace: DEFAULT_RUNTIME_LANE_WORKSPACE,
workspaceIsolation: "workspace dirty/stale state must not select CI/CD source commits",
branch: V02_SOURCE_BRANCH,
branch: DEFAULT_RUNTIME_LANE_SOURCE_BRANCH,
namespace: CI_NAMESPACE,
runtimeNamespace: "hwlab-v02",
pipeline: V02_PIPELINE,
pipeline: DEFAULT_RUNTIME_LANE_PIPELINE,
trigger: "manual UniDesk CLI PipelineRun creation",
argoApplication: V02_APP,
argoApplication: DEFAULT_RUNTIME_LANE_APP,
},
commitAlignment,
targetValidation,
@@ -329,12 +329,12 @@ export function v02ControlPlaneStatus(target: V02ControlPlaneStatusTarget = {}):
};
return {
...status,
closeout: v02CloseoutVerdict(status),
closeout: defaultRuntimeLaneCloseoutVerdict(status),
};
}
export function runtimeLaneControlPlaneStatusBundle(spec: HwlabRuntimeLaneSpec, target: V02ControlPlaneStatusTarget = {}): CommandJsonResult {
const targetMode: V02StatusTargetMode = target.mode
export function runtimeLaneControlPlaneStatusBundle(spec: HwlabRuntimeLaneSpec, target: DefaultRuntimeLaneControlPlaneStatusTarget = {}): CommandJsonResult {
const targetMode: DefaultRuntimeLaneStatusTargetMode = target.mode
?? (target.pipelineRun !== undefined && target.pipelineRun !== null ? "pipeline-run" : target.sourceCommit !== undefined ? "source-commit" : "latest-source-head");
const targetInit = target.pipelineRun !== undefined && target.pipelineRun !== null
? [
@@ -386,7 +386,7 @@ export function runtimeLaneControlPlaneStatusBundle(spec: HwlabRuntimeLaneSpec,
shellQuote(pipelineRunRowsJsonPath()),
].join(" "),
].join("\n");
return g14K3s(["sh", "--", script], 120_000);
return legacyRuntimeK3s(["sh", "--", script], 120_000);
}
export function runtimeLanePublicProbeScript(spec: HwlabRuntimeLaneSpec): string {
@@ -423,8 +423,8 @@ export function runtimeLanePublicProbeScript(spec: HwlabRuntimeLaneSpec): string
].join("\n");
}
export function runtimeLaneControlPlaneStatus(spec: HwlabRuntimeLaneSpec, target: V02ControlPlaneStatusTarget = {}): Record<string, unknown> {
const targetMode: V02StatusTargetMode = target.mode
export function runtimeLaneControlPlaneStatus(spec: HwlabRuntimeLaneSpec, target: DefaultRuntimeLaneControlPlaneStatusTarget = {}): Record<string, unknown> {
const targetMode: DefaultRuntimeLaneStatusTargetMode = target.mode
?? (target.pipelineRun !== undefined && target.pipelineRun !== null ? "pipeline-run" : target.sourceCommit !== undefined ? "source-commit" : "latest-source-head");
const bundle = runtimeLaneControlPlaneStatusBundle(spec, target);
const sections = parseShellSections(statusText(bundle));
@@ -638,8 +638,8 @@ export function runtimeLaneArgoRefreshScript(spec: HwlabRuntimeLaneSpec, dryRun:
].join("\n");
}
export function refreshRuntimeLaneArgoApplication(spec: HwlabRuntimeLaneSpec, options: G14ControlPlaneOptions): Record<string, unknown> {
const result = g14K3s(["sh", "--", runtimeLaneArgoRefreshScript(spec, options.dryRun)], options.timeoutSeconds * 1000);
export function refreshRuntimeLaneArgoApplication(spec: HwlabRuntimeLaneSpec, options: LegacyRuntimeControlPlaneOptions): Record<string, unknown> {
const result = legacyRuntimeK3s(["sh", "--", runtimeLaneArgoRefreshScript(spec, options.dryRun)], options.timeoutSeconds * 1000);
const fields = keyValueLinesFromText(statusText(result));
const ok = isCommandSuccess(result);
return {
@@ -692,7 +692,7 @@ export function refreshRuntimeLaneArgoApplication(spec: HwlabRuntimeLaneSpec, op
};
}
export function runRuntimeLaneControlPlane(spec: HwlabRuntimeLaneSpec, options: G14ControlPlaneOptions): Record<string, unknown> {
export function runRuntimeLaneControlPlane(spec: HwlabRuntimeLaneSpec, options: LegacyRuntimeControlPlaneOptions): Record<string, unknown> {
if (options.action === "cleanup-runs") return runControlPlaneCleanup(options);
if (options.action === "closeout" || options.action === "runtime-migration" || options.action === "cleanup-released-pvs") {
return {
@@ -1054,33 +1054,33 @@ export function runRuntimeLaneControlPlane(spec: HwlabRuntimeLaneSpec, options:
};
}
export function runV02ControlPlane(options: G14ControlPlaneOptions): Record<string, unknown> {
export function runDefaultRuntimeLaneControlPlane(options: LegacyRuntimeControlPlaneOptions): Record<string, unknown> {
if (options.action === "cleanup-runs") return runControlPlaneCleanup(options);
if (options.action === "cleanup-released-pvs") return runControlPlaneReleasedPvCleanup(options);
if ((options.action === "status" || options.action === "closeout") && options.pipelineRun !== undefined) {
const status = v02ControlPlaneStatus({ pipelineRun: options.pipelineRun, mode: "pipeline-run", includeHistory: options.history });
if (options.action === "closeout") return v02ControlPlaneCloseout(status);
const status = defaultRuntimeLaneControlPlaneStatus({ pipelineRun: options.pipelineRun, mode: "pipeline-run", includeHistory: options.history });
if (options.action === "closeout") return defaultRuntimeLaneControlPlaneCloseout(status);
return status;
}
if ((options.action === "status" || options.action === "closeout") && options.sourceCommit !== undefined) {
const status = v02ControlPlaneStatus({ sourceCommit: options.sourceCommit, mode: "source-commit", includeHistory: options.history });
if (options.action === "closeout") return v02ControlPlaneCloseout(status);
const status = defaultRuntimeLaneControlPlaneStatus({ sourceCommit: options.sourceCommit, mode: "source-commit", includeHistory: options.history });
if (options.action === "closeout") return defaultRuntimeLaneControlPlaneCloseout(status);
return status;
}
if (options.action === "closeout") {
const status = v02ControlPlaneStatus({ includeHistory: options.history });
return v02ControlPlaneCloseout(status);
const status = defaultRuntimeLaneControlPlaneStatus({ includeHistory: options.history });
return defaultRuntimeLaneControlPlaneCloseout(status);
}
const snapshot = options.action === "trigger-current" ? getV02TriggerSnapshot() : null;
const latestHead = options.action === "trigger-current" ? resolveV02LatestRemoteHead() : null;
const snapshot = options.action === "trigger-current" ? getDefaultRuntimeLaneTriggerSnapshot() : null;
const latestHead = options.action === "trigger-current" ? resolveDefaultRuntimeLaneLatestRemoteHead() : null;
const head = snapshot === null
? resolveV02Head()
? resolveDefaultRuntimeLaneHead()
: snapshot.sourceCommit === null
? resolveV02Head()
? resolveDefaultRuntimeLaneHead()
: { sourceCommit: snapshot.sourceCommit, result: snapshot.result };
let sourceCommit = head.sourceCommit;
if (options.action === "trigger-current" && latestHead !== null && latestHead.sourceCommit !== null && latestHead.sourceCommit !== sourceCommit) {
const refreshedHead = resolveV02Head();
const refreshedHead = resolveDefaultRuntimeLaneHead();
sourceCommit = refreshedHead.sourceCommit;
if (sourceCommit !== latestHead.sourceCommit) {
return {
@@ -1100,13 +1100,13 @@ export function runV02ControlPlane(options: G14ControlPlaneOptions): Record<stri
}
}
if (sourceCommit === null) {
return { ok: false, command: `hwlab g14 control-plane ${options.action} --lane v02`, degradedReason: "v02-head-unresolved", sourceRepo: V02_CICD_REPO, workspace: V02_WORKSPACE, headProbe: compactCommandResult(head.result) };
return { ok: false, command: `hwlab g14 control-plane ${options.action} --lane v02`, degradedReason: "v02-head-unresolved", sourceRepo: DEFAULT_RUNTIME_LANE_CICD_REPO, workspace: DEFAULT_RUNTIME_LANE_WORKSPACE, headProbe: compactCommandResult(head.result) };
}
if (options.action === "runtime-migration") return runV02RuntimeMigration(options, sourceCommit);
if (options.action === "status") return v02ControlPlaneStatus({ sourceCommit, mode: "latest-source-head", includeHistory: options.history });
if (options.action === "refresh") return refreshRuntimeLaneArgoApplication(V02_LANE_SPEC, options);
if (options.action === "runtime-migration") return runDefaultRuntimeLaneRuntimeMigration(options, sourceCommit);
if (options.action === "status") return defaultRuntimeLaneControlPlaneStatus({ sourceCommit, mode: "latest-source-head", includeHistory: options.history });
if (options.action === "refresh") return refreshRuntimeLaneArgoApplication(DEFAULT_RUNTIME_LANE_LANE_SPEC, options);
if (options.action === "apply") {
const render = runV02RenderToTemp(sourceCommit);
const render = runDefaultRuntimeLaneRenderToTemp(sourceCommit);
if (!isCommandSuccess(render.result)) {
return {
ok: false,
@@ -1117,8 +1117,8 @@ export function runV02ControlPlane(options: G14ControlPlaneOptions): Record<stri
render: compactCommandResult(render.result),
};
}
const apply = applyV02ControlPlaneFiles(render.renderDir, options.dryRun, options.timeoutSeconds);
const cleanupRenderDir = isCommandSuccess(apply) ? cleanupV02RenderDir(render.renderDir) : null;
const apply = applyDefaultRuntimeLaneControlPlaneFiles(render.renderDir, options.dryRun, options.timeoutSeconds);
const cleanupRenderDir = isCommandSuccess(apply) ? cleanupDefaultRuntimeLaneRenderDir(render.renderDir) : null;
return {
ok: isCommandSuccess(apply),
command: "hwlab g14 control-plane apply --lane v02",
@@ -1129,8 +1129,8 @@ export function runV02ControlPlane(options: G14ControlPlaneOptions): Record<stri
render: compactCommandResult(render.result),
apply: compactCommandResult(apply),
cleanupRenderDir: cleanupRenderDir === null ? null : compactCommandResult(cleanupRenderDir),
cleanupObsoleteCronJobs: compactCommandResult(options.dryRun ? deleteV02ObsoleteCronJobs(true) : deleteV02ObsoleteCronJobs(false)),
status: v02ControlPlaneStatus({ sourceCommit, mode: "latest-source-head" }),
cleanupObsoleteCronJobs: compactCommandResult(options.dryRun ? deleteDefaultRuntimeLaneObsoleteCronJobs(true) : deleteDefaultRuntimeLaneObsoleteCronJobs(false)),
status: defaultRuntimeLaneControlPlaneStatus({ sourceCommit, mode: "latest-source-head" }),
next: options.dryRun
? { apply: "bun scripts/cli.ts hwlab g14 control-plane apply --lane v02 --confirm" }
: { triggerCurrent: "bun scripts/cli.ts hwlab g14 control-plane trigger-current --lane v02 --confirm" },
@@ -1138,16 +1138,16 @@ export function runV02ControlPlane(options: G14ControlPlaneOptions): Record<stri
}
let before = snapshot !== null && snapshot.sourceCommit === sourceCommit && snapshot.before !== null
? snapshot.before
: getPipelineRunCompact(v02PipelineRunName(sourceCommit));
: getPipelineRunCompact(defaultRuntimeLanePipelineRunName(sourceCommit));
if (options.dryRun) {
const gitMirrorPreSync = preSyncV02GitMirror(sourceCommit, options);
const gitMirrorPreSync = preSyncDefaultRuntimeLaneGitMirror(sourceCommit, options);
return {
ok: true,
command: "hwlab g14 control-plane trigger-current --lane v02",
lane: "v02",
mode: "dry-run",
sourceCommit,
pipelineRun: v02PipelineRunName(sourceCommit),
pipelineRun: defaultRuntimeLanePipelineRunName(sourceCommit),
before,
gitMirrorPreSync,
controlPlaneRefresh: {
@@ -1155,13 +1155,13 @@ export function runV02ControlPlane(options: G14ControlPlaneOptions): Record<stri
wouldRefreshBeforeCreate: true,
resources: ["tekton-v02/rbac.yaml", "tekton-v02/pipeline.yaml", "argocd/project.yaml", "argocd/application-v02.yaml"],
},
manifest: v02PipelineRunManifest(sourceCommit),
manifest: defaultRuntimeLanePipelineRunManifest(sourceCommit),
next: { triggerCurrent: "bun scripts/cli.ts hwlab g14 control-plane trigger-current --lane v02 --confirm" },
};
}
if (before.exists === true && before.status !== null) {
const reuseHead = latestHead ?? resolveV02LatestRemoteHead();
const reuseDecision = v02ExistingPipelineRunReuseDecision({ sourceCommit, before, latestSourceCommit: reuseHead.sourceCommit });
const reuseHead = latestHead ?? resolveDefaultRuntimeLaneLatestRemoteHead();
const reuseDecision = defaultRuntimeLaneExistingPipelineRunReuseDecision({ sourceCommit, before, latestSourceCommit: reuseHead.sourceCommit });
if (reuseDecision.reusable === true) {
const alreadyUsable = reuseDecision.alreadyUsable === true;
return {
@@ -1170,7 +1170,7 @@ export function runV02ControlPlane(options: G14ControlPlaneOptions): Record<stri
lane: "v02",
mode: "confirmed-trigger",
sourceCommit,
pipelineRun: v02PipelineRunName(sourceCommit),
pipelineRun: defaultRuntimeLanePipelineRunName(sourceCommit),
before,
skipped: true,
reuseDecision,
@@ -1185,11 +1185,11 @@ export function runV02ControlPlane(options: G14ControlPlaneOptions): Record<stri
status: "advanced",
sourceCommit,
latestSourceCommit: reuseHead.sourceCommit,
previousPipelineRun: v02PipelineRunName(sourceCommit),
latestPipelineRun: v02PipelineRunName(reuseHead.sourceCommit),
previousPipelineRun: defaultRuntimeLanePipelineRunName(sourceCommit),
latestPipelineRun: defaultRuntimeLanePipelineRunName(reuseHead.sourceCommit),
});
sourceCommit = reuseHead.sourceCommit;
before = getPipelineRunCompact(v02PipelineRunName(sourceCommit));
before = getPipelineRunCompact(defaultRuntimeLanePipelineRunName(sourceCommit));
if (before.exists === true && before.status !== null) {
const advancedAlreadyUsable = before.status === "True" || before.status === "Unknown";
return {
@@ -1198,13 +1198,13 @@ export function runV02ControlPlane(options: G14ControlPlaneOptions): Record<stri
lane: "v02",
mode: "confirmed-trigger",
sourceCommit,
pipelineRun: v02PipelineRunName(sourceCommit),
pipelineRun: defaultRuntimeLanePipelineRunName(sourceCommit),
before,
skipped: true,
reuseDecision: {
...reuseDecision,
advancedSourceCommit: sourceCommit,
advancedPipelineRun: v02PipelineRunName(sourceCommit),
advancedPipelineRun: defaultRuntimeLanePipelineRunName(sourceCommit),
advancedPipelineRunStatus: before.status ?? null,
},
reason: advancedAlreadyUsable ? "advanced-existing-pipelinerun-reused" : "advanced-existing-pipelinerun-terminal-failed",
@@ -1219,7 +1219,7 @@ export function runV02ControlPlane(options: G14ControlPlaneOptions): Record<stri
lane: "v02",
mode: "confirmed-trigger",
sourceCommit,
pipelineRun: v02PipelineRunName(sourceCommit),
pipelineRun: defaultRuntimeLanePipelineRunName(sourceCommit),
before,
reuseDecision,
degradedReason: "existing-pipelinerun-reuse-rejected",
@@ -1239,7 +1239,7 @@ export function runV02ControlPlane(options: G14ControlPlaneOptions): Record<stri
mode: "confirmed-trigger",
phase: "source-head-recheck",
sourceCommit,
pipelineRun: v02PipelineRunName(sourceCommit),
pipelineRun: defaultRuntimeLanePipelineRunName(sourceCommit),
before,
latestSourceHeadProbe: {
sourceCommit: latestHead.sourceCommit,
@@ -1251,13 +1251,13 @@ export function runV02ControlPlane(options: G14ControlPlaneOptions): Record<stri
: "source-head-advanced-before-pipelinerun-create",
};
}
printProgressEvent("hwlab.v02.trigger.progress", { stage: "control-plane-refresh", status: "started", sourceCommit, pipelineRun: v02PipelineRunName(sourceCommit) });
const controlPlaneRefresh = refreshV02ControlPlaneBeforeTrigger(sourceCommit, options.timeoutSeconds);
printProgressEvent("hwlab.v02.trigger.progress", { stage: "control-plane-refresh", status: "started", sourceCommit, pipelineRun: defaultRuntimeLanePipelineRunName(sourceCommit) });
const controlPlaneRefresh = refreshDefaultRuntimeLaneControlPlaneBeforeTrigger(sourceCommit, options.timeoutSeconds);
printProgressEvent("hwlab.v02.trigger.progress", {
stage: "control-plane-refresh",
status: controlPlaneRefresh.ok === true ? "succeeded" : "failed",
sourceCommit,
pipelineRun: v02PipelineRunName(sourceCommit),
pipelineRun: defaultRuntimeLanePipelineRunName(sourceCommit),
mode: record(controlPlaneRefresh).mode ?? null,
waitedMs: record(controlPlaneRefresh).waitedMs ?? null,
renderDir: record(controlPlaneRefresh).renderDir ?? null,
@@ -1271,7 +1271,7 @@ export function runV02ControlPlane(options: G14ControlPlaneOptions): Record<stri
mode: "confirmed-trigger",
phase: "control-plane-refresh",
sourceCommit,
pipelineRun: v02PipelineRunName(sourceCommit),
pipelineRun: defaultRuntimeLanePipelineRunName(sourceCommit),
before,
controlPlaneRefresh,
degradedReason: record(controlPlaneRefresh).degradedReason ?? "control-plane-refresh-failed",
@@ -1281,19 +1281,19 @@ export function runV02ControlPlane(options: G14ControlPlaneOptions): Record<stri
stage: "git-mirror-pre-sync",
status: "started",
sourceCommit,
pipelineRun: v02PipelineRunName(sourceCommit),
pipelineRun: defaultRuntimeLanePipelineRunName(sourceCommit),
reason: "ensure local git mirror has source commit before creating PipelineRun",
});
const gitMirrorPreSync = preSyncV02GitMirror(sourceCommit, options);
const gitMirrorPreSync = preSyncDefaultRuntimeLaneGitMirror(sourceCommit, options);
printProgressEvent("hwlab.v02.trigger.progress", {
stage: "git-mirror-pre-sync",
status: gitMirrorPreSync.ok === true ? "succeeded" : "failed",
sourceCommit,
pipelineRun: v02PipelineRunName(sourceCommit),
pipelineRun: defaultRuntimeLanePipelineRunName(sourceCommit),
mode: record(gitMirrorPreSync).mode ?? null,
required: nested(gitMirrorPreSync, ["before", "required"]) ?? null,
localV02: nested(gitMirrorPreSync, ["before", "localV02"]) ?? null,
finalLocalV02: nested(gitMirrorPreSync, ["final", "localV02"]) ?? nested(gitMirrorPreSync, ["marker", "localV02"]) ?? nested(gitMirrorPreSync, ["after", "localV02"]) ?? null,
localDefaultRuntimeLane: nested(gitMirrorPreSync, ["before", "localDefaultRuntimeLane"]) ?? null,
finalLocalDefaultRuntimeLane: nested(gitMirrorPreSync, ["final", "localDefaultRuntimeLane"]) ?? nested(gitMirrorPreSync, ["marker", "localDefaultRuntimeLane"]) ?? nested(gitMirrorPreSync, ["after", "localDefaultRuntimeLane"]) ?? null,
pendingFlush: nested(gitMirrorPreSync, ["before", "pendingFlush"]) ?? null,
reason: nested(gitMirrorPreSync, ["before", "reason"]) ?? null,
syncElapsedMs: nested(gitMirrorPreSync, ["sync", "elapsedMs"]) ?? null,
@@ -1310,28 +1310,28 @@ export function runV02ControlPlane(options: G14ControlPlaneOptions): Record<stri
mode: "confirmed-trigger",
phase: "git-mirror-pre-sync",
sourceCommit,
pipelineRun: v02PipelineRunName(sourceCommit),
pipelineRun: defaultRuntimeLanePipelineRunName(sourceCommit),
before,
controlPlaneRefresh,
gitMirrorPreSync,
degradedReason: "git-mirror-pre-sync-failed",
};
}
printProgressEvent("hwlab.v02.trigger.progress", { stage: "create-pipelinerun", status: "started", sourceCommit, pipelineRun: v02PipelineRunName(sourceCommit) });
const createPipelineRun = createV02PipelineRun(sourceCommit, options.timeoutSeconds);
printProgressEvent("hwlab.v02.trigger.progress", { stage: "create-pipelinerun", status: isCommandSuccess(createPipelineRun) ? "succeeded" : "failed", sourceCommit, pipelineRun: v02PipelineRunName(sourceCommit), exitCode: createPipelineRun.exitCode });
printProgressEvent("hwlab.v02.trigger.progress", { stage: "create-pipelinerun", status: "started", sourceCommit, pipelineRun: defaultRuntimeLanePipelineRunName(sourceCommit) });
const createPipelineRun = createDefaultRuntimeLanePipelineRun(sourceCommit, options.timeoutSeconds);
printProgressEvent("hwlab.v02.trigger.progress", { stage: "create-pipelinerun", status: isCommandSuccess(createPipelineRun) ? "succeeded" : "failed", sourceCommit, pipelineRun: defaultRuntimeLanePipelineRunName(sourceCommit), exitCode: createPipelineRun.exitCode });
return {
ok: isCommandSuccess(createPipelineRun),
command: "hwlab g14 control-plane trigger-current --lane v02",
lane: "v02",
mode: "confirmed-trigger",
sourceCommit,
pipelineRun: v02PipelineRunName(sourceCommit),
pipelineRun: defaultRuntimeLanePipelineRunName(sourceCommit),
before,
controlPlaneRefresh: compactControlPlaneRefresh(controlPlaneRefresh),
gitMirrorPreSync,
createPipelineRun: compactTriggerCommandResult(compactCommandResult(createPipelineRun)),
after: getPipelineRunCompact(v02PipelineRunName(sourceCommit)),
after: getPipelineRunCompact(defaultRuntimeLanePipelineRunName(sourceCommit)),
latestOnlyPolicy: "no old PipelineRun is canceled; stale commits self-supersede before GitOps writeback",
disclosure: {
fullTriggerOutput: "Use the async job stdout/stderr files from job status for full command details.",
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. entry module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. entry module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:10619-12000 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:10619-12000 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -10,23 +10,23 @@ import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import { runRuntimeLaneControlPlane, runV02ControlPlane } from "./control-plane";
import { runG14GitMirror } from "./git-mirror";
import { hwlabG14Help, monitorStatus } from "./help";
import { runG14ToolsImage, runG14UpstreamImage, startControlPlaneTriggerJob, startGitMirrorJob } from "./images";
import { runRuntimeLaneControlPlane, runDefaultRuntimeLaneControlPlane } from "./control-plane";
import { runLegacyRuntimeGitMirror } from "./git-mirror";
import { hwlabLegacyRuntimeHelp, monitorStatus } from "./help";
import { runLegacyRuntimeToolsImage, runLegacyRuntimeUpstreamImage, startControlPlaneTriggerJob, startGitMirrorJob } from "./images";
import { runMonitorWorker } from "./monitor";
import { runG14Observability } from "./observability";
import { hwlabG14MonitorStateFileName, hwlabG14MonitorStateRole, parseControlPlaneOptions, parseGitMirrorOptions, parseLegacyRetirementOptions, parseObservabilityOptions, parseOptions, parseSecretOptions, parseToolsImageOptions, parseUpstreamImageOptions } from "./options";
import { runLegacyRuntimeObservability } from "./observability";
import { hwlabLegacyRuntimeMonitorStateFileName, hwlabLegacyRuntimeMonitorStateRole, parseControlPlaneOptions, parseGitMirrorOptions, parseLegacyRetirementOptions, parseObservabilityOptions, parseOptions, parseSecretOptions, parseToolsImageOptions, parseUpstreamImageOptions } from "./options";
import { monitorBaseBranch } from "./remote";
import { legacyG14RecordRolloutRetired, legacyG14RetirementBlocksMonitor, runLegacyG14Retirement } from "./retirement";
import { runG14Secret } from "./secrets";
import { G14_SOURCE_BRANCH, HWLAB_REPO } from "./types";
import { legacyLegacyRuntimeRecordRolloutRetired, legacyLegacyRuntimeRetirementBlocksMonitor, runLegacyLegacyRuntimeRetirement } from "./retirement";
import { runLegacyRuntimeSecret } from "./secrets";
import { LEGACY_RUNTIME_SOURCE_BRANCH, HWLAB_REPO } from "./types";
export async function runHwlabG14Command(_config: Config, args: string[]): Promise<Record<string, unknown>> {
if (args.length === 0 || args.includes("--help") || args.includes("-h")) return { ok: true, ...hwlabG14Help() };
export async function runHwlabLegacyRuntimeCommand(_config: Config, args: string[]): Promise<Record<string, unknown>> {
if (args.length === 0 || args.includes("--help") || args.includes("-h")) return { ok: true, ...hwlabLegacyRuntimeHelp() };
const [action] = args;
if (action === "record-rollout") {
return legacyG14RecordRolloutRetired();
return legacyLegacyRuntimeRecordRolloutRetired();
}
if (action === "control-plane") {
const options = parseControlPlaneOptions(args.slice(1));
@@ -36,34 +36,34 @@ export async function runHwlabG14Command(_config: Config, args: string[]): Promi
if (isHwlabRuntimeLane(options.lane) && options.lane !== "v02") {
return runRuntimeLaneControlPlane(hwlabRuntimeLaneSpec(options.lane), options);
}
return runV02ControlPlane(options);
return runDefaultRuntimeLaneControlPlane(options);
}
if (action === "retirement") {
const options = parseLegacyRetirementOptions(args.slice(1));
return runLegacyG14Retirement(options);
return runLegacyLegacyRuntimeRetirement(options);
}
if (action === "secret") {
const options = parseSecretOptions(args.slice(1));
return runG14Secret(options);
return runLegacyRuntimeSecret(options);
}
if (action === "tools-image") {
const options = parseToolsImageOptions(args.slice(1));
return runG14ToolsImage(options);
return runLegacyRuntimeToolsImage(options);
}
if (action === "upstream-image") {
const options = parseUpstreamImageOptions(args.slice(1));
return runG14UpstreamImage(options);
return runLegacyRuntimeUpstreamImage(options);
}
if (action === "git-mirror") {
const options = parseGitMirrorOptions(args.slice(1));
if ((options.action === "sync" || options.action === "flush") && options.confirm && !options.dryRun && !options.wait) {
return startGitMirrorJob(options);
}
return runG14GitMirror(options);
return runLegacyRuntimeGitMirror(options);
}
if (action === "observability") {
const options = parseObservabilityOptions(args.slice(1));
return runG14Observability(options);
return runLegacyRuntimeObservability(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 retirement, hwlab g14 control-plane, hwlab g14 secret, hwlab g14 git-mirror, hwlab g14 observability, hwlab g14 tools-image, hwlab g14 upstream-image" };
@@ -71,7 +71,7 @@ export async function runHwlabG14Command(_config: Config, args: string[]): Promi
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;
const retirement = options.lane === "g14" ? legacyLegacyRuntimeRetirementBlocksMonitor() : null;
if (retirement !== null) {
return {
ok: false,
@@ -83,7 +83,7 @@ export async function runHwlabG14Command(_config: Config, args: string[]): Promi
retirement,
next: {
retirementStatus: "bun scripts/cli.ts hwlab g14 retirement status",
v02Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02",
defaultRuntimeLaneMonitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02",
v03Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v03",
},
};
@@ -91,14 +91,14 @@ export async function runHwlabG14Command(_config: Config, args: string[]): Promi
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 === "g14" ? "hwlab_g14_pr_monitor" : `hwlab_g14_${options.lane}_pr_monitor`;
const jobNote = options.lane === "g14"
? `Monitor ${HWLAB_REPO} PRs targeting ${G14_SOURCE_BRANCH} and roll merged changes to G14 DEV`
? `Monitor ${HWLAB_REPO} PRs targeting ${LEGACY_RUNTIME_SOURCE_BRANCH} and roll merged changes to G14 DEV`
: `Monitor ${HWLAB_REPO} PRs targeting ${monitorBaseBranch(options.lane)}, merge ready PRs, trigger ${hwlabRuntimeLaneSpec(options.lane).version} CD, comment PR progress, and file failure issues`;
const job = startJob(jobName, command, jobNote);
const statusCommand = `bun scripts/cli.ts job status ${job.id} --tail-bytes 30000`;
const stateDir = rootPath(".state", "hwlab-g14");
mkdirSync(stateDir, { recursive: true });
const stateFileName = hwlabG14MonitorStateFileName(options);
const stateFileRole = hwlabG14MonitorStateRole(options);
const stateFileName = hwlabLegacyRuntimeMonitorStateFileName(options);
const stateFileRole = hwlabLegacyRuntimeMonitorStateRole(options);
const latestPath = join(stateDir, stateFileName);
const previousLatest = existsSync(latestPath) ? readFileSync(latestPath, "utf8") : null;
writeFileSync(latestPath, `${JSON.stringify({ jobId: job.id, createdAt: job.createdAt, statusCommand, role: stateFileRole, lane: options.lane, baseBranch: monitorBaseBranch(options.lane) }, null, 2)}\n`, "utf8");
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. git-mirror module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. git-mirror module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:5846-6777 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:5846-6777 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -10,17 +10,17 @@ import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import type { CommandJsonResult, G14ControlPlaneOptions, G14GitMirrorOptions } from "./types";
import type { CommandJsonResult, LegacyRuntimeControlPlaneOptions, LegacyRuntimeGitMirrorOptions } from "./types";
import { tailText } from "./cleanup";
import { statusText } from "./pr-monitor";
import { compactCommandResult, g14K3s, isCommandSuccess, nested, printProgressEvent, record, shellQuote, shortSha, stringOrNull } from "./remote";
import { compactCommandResult, legacyRuntimeK3s, isCommandSuccess, nested, printProgressEvent, record, shellQuote, shortSha, stringOrNull } from "./remote";
import { cleanupRuntimeLaneRenderDir, runRuntimeLaneRenderToTemp } from "./render";
import { resolveRuntimeLaneHead } from "./source";
import { GIT_MIRROR_LEGACY_CRONJOB, GIT_MIRROR_MANIFEST_FIELD_MANAGER, GIT_MIRROR_NAMESPACE, GIT_MIRROR_SYNC_JOB_PREFIX, V02_GIT_MIRROR_PRESYNC_LOCK_STALE_MS, V02_GIT_MIRROR_PRESYNC_MAX_WAIT_MS, V02_GIT_MIRROR_PRESYNC_POLL_MS, V02_GIT_READ_URL, V02_GIT_WRITE_URL } from "./types";
import { parseShellSections, shellSectionOk, timestampMs } from "./v02-status";
import { GIT_MIRROR_LEGACY_CRONJOB, GIT_MIRROR_MANIFEST_FIELD_MANAGER, GIT_MIRROR_NAMESPACE, GIT_MIRROR_SYNC_JOB_PREFIX, DEFAULT_RUNTIME_LANE_GIT_MIRROR_PRESYNC_LOCK_STALE_MS, DEFAULT_RUNTIME_LANE_GIT_MIRROR_PRESYNC_MAX_WAIT_MS, DEFAULT_RUNTIME_LANE_GIT_MIRROR_PRESYNC_POLL_MS, DEFAULT_RUNTIME_LANE_GIT_READ_URL, DEFAULT_RUNTIME_LANE_GIT_WRITE_URL } from "./types";
import { parseShellSections, shellSectionOk, timestampMs } from "./runtime-status";
export function deleteLegacyGitMirrorCronJob(dryRun: boolean): CommandJsonResult {
return g14K3s([
return legacyRuntimeK3s([
"kubectl",
"delete",
"cronjob",
@@ -33,7 +33,7 @@ export function deleteLegacyGitMirrorCronJob(dryRun: boolean): CommandJsonResult
}
export function applyGitMirrorManifestFile(renderDir: string, dryRun: boolean, timeoutSeconds: number): CommandJsonResult {
return g14K3s([
return legacyRuntimeK3s([
"kubectl",
"apply",
"--server-side",
@@ -131,10 +131,10 @@ export function parseGitMirrorStatusRefs(raw: string): { refs: Record<string, st
return {
refs: {
...parsedRefs,
localV02: stringOrNull(refs.localV02),
githubV02: stringOrNull(refs.githubV02),
localG14: stringOrNull(refs.localG14),
githubG14: stringOrNull(refs.githubG14),
localDefaultRuntimeLane: stringOrNull(refs.localDefaultRuntimeLane),
githubDefaultRuntimeLane: stringOrNull(refs.githubDefaultRuntimeLane),
localLegacyRuntime: stringOrNull(refs.localLegacyRuntime),
githubLegacyRuntime: stringOrNull(refs.githubLegacyRuntime),
localGitops: stringOrNull(refs.localGitops),
githubGitops: stringOrNull(refs.githubGitops),
localV03: stringOrNull(refs.localV03),
@@ -166,15 +166,15 @@ export function gitMirrorStatusSummary(raw: string): Record<string, unknown> {
const lastSync = parseLabeledJsonLine(raw, "lastSync");
const lastWrite = parseLabeledJsonLine(raw, "lastWrite");
const lastFlush = parseLabeledJsonLine(raw, "lastFlush");
const localV02 = refs.refs.localV02 ?? null;
const githubV02 = refs.refs.githubV02 ?? null;
const localDefaultRuntimeLane = refs.refs.localDefaultRuntimeLane ?? null;
const githubDefaultRuntimeLane = refs.refs.githubDefaultRuntimeLane ?? null;
const localGitops = refs.refs.localGitops ?? null;
const githubGitops = refs.refs.githubGitops ?? null;
const localV03 = refs.refs.localV03 ?? null;
const githubV03 = refs.refs.githubV03 ?? null;
const localV03Gitops = refs.refs.localV03Gitops ?? null;
const githubV03Gitops = refs.refs.githubV03Gitops ?? null;
const sourceInSync = Boolean(localV02 && githubV02 && localV02 === githubV02);
const sourceInSync = Boolean(localDefaultRuntimeLane && githubDefaultRuntimeLane && localDefaultRuntimeLane === githubDefaultRuntimeLane);
const gitopsInSync = Boolean(localGitops && githubGitops && localGitops === githubGitops);
const runtimeLanes = Object.fromEntries(hwlabRuntimeLaneIds().map((lane) => {
const normalized = lane.slice(0, 1).toUpperCase() + lane.slice(1).toLowerCase();
@@ -196,12 +196,12 @@ export function gitMirrorStatusSummary(raw: string): Record<string, unknown> {
return laneSummary.sourceInSync === true && laneSummary.gitopsInSync === true;
});
return {
localV02,
githubV02,
localDefaultRuntimeLane,
githubDefaultRuntimeLane,
localV03,
githubV03,
localG14: refs.refs.localG14 ?? null,
githubG14: refs.refs.githubG14 ?? null,
localLegacyRuntime: refs.refs.localLegacyRuntime ?? null,
githubLegacyRuntime: refs.refs.githubLegacyRuntime ?? null,
localGitops,
githubGitops,
localV03Gitops,
@@ -234,21 +234,21 @@ export function gitMirrorStatusSummary(raw: string): Record<string, unknown> {
};
}
export function gitMirrorV02SyncRequirement(sourceCommit: string, rawStatus: string): Record<string, unknown> {
export function gitMirrorDefaultRuntimeLaneSyncRequirement(sourceCommit: string, rawStatus: string): Record<string, unknown> {
const parsed = parseGitMirrorStatusRefs(rawStatus);
const localV02 = parsed.refs.localV02 ?? null;
const localDefaultRuntimeLane = parsed.refs.localDefaultRuntimeLane ?? null;
return {
required: localV02 !== sourceCommit,
required: localDefaultRuntimeLane !== sourceCommit,
sourceCommit,
localV02,
localDefaultRuntimeLane,
pendingFlush: parsed.pendingFlush,
refs: parsed.refs,
reason: localV02 === sourceCommit ? "local-v02-current" : localV02 === null ? "local-v02-unresolved" : "local-v02-stale",
reason: localDefaultRuntimeLane === sourceCommit ? "local-v02-current" : localDefaultRuntimeLane === null ? "local-v02-unresolved" : "local-v02-stale",
};
}
export function runtimeLaneGitMirrorSourceInSyncForTest(lane: string, sourceCommit: string, summary: Record<string, unknown>): boolean {
const localKey = lane === "v02" ? "localV02" : lane === "v03" ? "localV03" : null;
const localKey = lane === "v02" ? "localDefaultRuntimeLane" : lane === "v03" ? "localV03" : null;
return localKey !== null && typeof summary[localKey] === "string" && summary[localKey] === sourceCommit;
}
@@ -261,19 +261,19 @@ export function runtimeLaneGitMirrorSourceInSync(spec: HwlabRuntimeLaneSpec, sou
}
export function compactGitMirrorStatus(status: Record<string, unknown>, sourceCommit: string): Record<string, unknown> {
const requirement = gitMirrorV02SyncRequirement(sourceCommit, gitMirrorStatusCacheRaw(status));
const requirement = gitMirrorDefaultRuntimeLaneSyncRequirement(sourceCommit, gitMirrorStatusCacheRaw(status));
return {
ok: status.ok === true,
readUrl: status.readUrl ?? V02_GIT_READ_URL,
writeUrl: status.writeUrl ?? V02_GIT_WRITE_URL,
readUrl: status.readUrl ?? DEFAULT_RUNTIME_LANE_GIT_READ_URL,
writeUrl: status.writeUrl ?? DEFAULT_RUNTIME_LANE_GIT_WRITE_URL,
elapsedMs: status.elapsedMs ?? null,
legacyCronJobExists: nested(status, ["legacyCronJob", "exists"]) === true,
required: requirement.required,
sourceCommit,
localV02: requirement.localV02 ?? null,
githubV02: requirement.refs.githubV02 ?? null,
localDefaultRuntimeLane: requirement.localDefaultRuntimeLane ?? null,
githubDefaultRuntimeLane: requirement.refs.githubDefaultRuntimeLane ?? null,
pendingFlush: requirement.pendingFlush ?? null,
sourceInSync: Boolean(requirement.refs.localV02 && requirement.refs.githubV02 && requirement.refs.localV02 === requirement.refs.githubV02),
sourceInSync: Boolean(requirement.refs.localDefaultRuntimeLane && requirement.refs.githubDefaultRuntimeLane && requirement.refs.localDefaultRuntimeLane === requirement.refs.githubDefaultRuntimeLane),
reason: requirement.reason,
cacheOk: nested(status, ["cache", "ok"]) === true,
cacheStderr: tailText(nested(status, ["cache", "stderr"]), 1000),
@@ -299,74 +299,74 @@ export function compactGitMirrorSync(sync: Record<string, unknown>): Record<stri
};
}
export interface V02GitMirrorPreSyncMarker {
export interface DefaultRuntimeLaneGitMirrorPreSyncMarker {
ok: boolean;
sourceCommit: string;
syncedAt: string;
localV02?: string | null;
githubV02?: string | null;
localDefaultRuntimeLane?: string | null;
githubDefaultRuntimeLane?: string | null;
reason?: string | null;
}
export function v02GitMirrorPreSyncStateDir(): string {
export function defaultRuntimeLaneGitMirrorPreSyncStateDir(): string {
return rootPath(".state", "hwlab-g14", "v02-git-mirror-presync");
}
export function v02GitMirrorPreSyncMarkerPath(sourceCommit: string): string {
return join(v02GitMirrorPreSyncStateDir(), `${shortSha(sourceCommit)}.json`);
export function defaultRuntimeLaneGitMirrorPreSyncMarkerPath(sourceCommit: string): string {
return join(defaultRuntimeLaneGitMirrorPreSyncStateDir(), `${shortSha(sourceCommit)}.json`);
}
export function v02GitMirrorPreSyncLockDir(sourceCommit: string): string {
return join(v02GitMirrorPreSyncStateDir(), `${shortSha(sourceCommit)}.lock`);
export function defaultRuntimeLaneGitMirrorPreSyncLockDir(sourceCommit: string): string {
return join(defaultRuntimeLaneGitMirrorPreSyncStateDir(), `${shortSha(sourceCommit)}.lock`);
}
export function v02GitMirrorPreSyncWaitMs(timeoutSeconds: number): number {
export function defaultRuntimeLaneGitMirrorPreSyncWaitMs(timeoutSeconds: number): number {
const requestedMs = Math.max(0, Math.floor(timeoutSeconds * 1000));
if (requestedMs === 0) return V02_GIT_MIRROR_PRESYNC_MAX_WAIT_MS;
return Math.min(V02_GIT_MIRROR_PRESYNC_MAX_WAIT_MS, requestedMs);
if (requestedMs === 0) return DEFAULT_RUNTIME_LANE_GIT_MIRROR_PRESYNC_MAX_WAIT_MS;
return Math.min(DEFAULT_RUNTIME_LANE_GIT_MIRROR_PRESYNC_MAX_WAIT_MS, requestedMs);
}
export function v02ReusableGitMirrorPreSyncMarker(marker: unknown, sourceCommit: string, nowMs = Date.now(), minSyncedAtMs = 0): V02GitMirrorPreSyncMarker | null {
const candidate = record(marker) as V02GitMirrorPreSyncMarker;
export function defaultRuntimeLaneReusableGitMirrorPreSyncMarker(marker: unknown, sourceCommit: string, nowMs = Date.now(), minSyncedAtMs = 0): DefaultRuntimeLaneGitMirrorPreSyncMarker | null {
const candidate = record(marker) as DefaultRuntimeLaneGitMirrorPreSyncMarker;
const syncedAtMs = timestampMs(candidate.syncedAt);
if (candidate.ok !== true || candidate.sourceCommit !== sourceCommit) return null;
if (syncedAtMs === null || nowMs - syncedAtMs > V02_GIT_MIRROR_PRESYNC_MAX_WAIT_MS) return null;
if (syncedAtMs === null || nowMs - syncedAtMs > DEFAULT_RUNTIME_LANE_GIT_MIRROR_PRESYNC_MAX_WAIT_MS) return null;
if (syncedAtMs < minSyncedAtMs) return null;
return candidate;
}
export function readV02GitMirrorPreSyncMarker(sourceCommit: string, nowMs = Date.now(), minSyncedAtMs = 0): V02GitMirrorPreSyncMarker | null {
const path = v02GitMirrorPreSyncMarkerPath(sourceCommit);
export function readDefaultRuntimeLaneGitMirrorPreSyncMarker(sourceCommit: string, nowMs = Date.now(), minSyncedAtMs = 0): DefaultRuntimeLaneGitMirrorPreSyncMarker | null {
const path = defaultRuntimeLaneGitMirrorPreSyncMarkerPath(sourceCommit);
if (!existsSync(path)) return null;
try {
return v02ReusableGitMirrorPreSyncMarker(JSON.parse(readFileSync(path, "utf8")) as unknown, sourceCommit, nowMs, minSyncedAtMs);
return defaultRuntimeLaneReusableGitMirrorPreSyncMarker(JSON.parse(readFileSync(path, "utf8")) as unknown, sourceCommit, nowMs, minSyncedAtMs);
} catch {
return null;
}
}
export function writeV02GitMirrorPreSyncMarker(sourceCommit: string, summary: Record<string, unknown>): V02GitMirrorPreSyncMarker {
export function writeDefaultRuntimeLaneGitMirrorPreSyncMarker(sourceCommit: string, summary: Record<string, unknown>): DefaultRuntimeLaneGitMirrorPreSyncMarker {
const marker = {
ok: true,
sourceCommit,
syncedAt: new Date().toISOString(),
localV02: stringOrNull(summary.localV02),
githubV02: stringOrNull(summary.githubV02),
localDefaultRuntimeLane: stringOrNull(summary.localDefaultRuntimeLane),
githubDefaultRuntimeLane: stringOrNull(summary.githubDefaultRuntimeLane),
reason: stringOrNull(summary.reason),
};
const dir = v02GitMirrorPreSyncStateDir();
const dir = defaultRuntimeLaneGitMirrorPreSyncStateDir();
mkdirSync(dir, { recursive: true });
writeFileSync(v02GitMirrorPreSyncMarkerPath(sourceCommit), `${JSON.stringify(marker, null, 2)}\n`, "utf8");
writeFileSync(defaultRuntimeLaneGitMirrorPreSyncMarkerPath(sourceCommit), `${JSON.stringify(marker, null, 2)}\n`, "utf8");
return marker;
}
export function acquireV02GitMirrorPreSyncLock(sourceCommit: string, waitMs: number, minSyncedAtMs = 0): { acquired: boolean; lockDir: string; waitedMs: number; marker?: V02GitMirrorPreSyncMarker } {
const stateDir = v02GitMirrorPreSyncStateDir();
export function acquireDefaultRuntimeLaneGitMirrorPreSyncLock(sourceCommit: string, waitMs: number, minSyncedAtMs = 0): { acquired: boolean; lockDir: string; waitedMs: number; marker?: DefaultRuntimeLaneGitMirrorPreSyncMarker } {
const stateDir = defaultRuntimeLaneGitMirrorPreSyncStateDir();
mkdirSync(stateDir, { recursive: true });
const lockDir = v02GitMirrorPreSyncLockDir(sourceCommit);
const lockDir = defaultRuntimeLaneGitMirrorPreSyncLockDir(sourceCommit);
const startedAtMs = Date.now();
for (;;) {
const marker = readV02GitMirrorPreSyncMarker(sourceCommit, Date.now(), minSyncedAtMs);
const marker = readDefaultRuntimeLaneGitMirrorPreSyncMarker(sourceCommit, Date.now(), minSyncedAtMs);
if (marker !== null) return { acquired: false, lockDir, waitedMs: Date.now() - startedAtMs, marker };
try {
mkdirSync(lockDir);
@@ -380,7 +380,7 @@ export function acquireV02GitMirrorPreSyncLock(sourceCommit: string, waitMs: num
return 0;
}
})();
if (ageMs > V02_GIT_MIRROR_PRESYNC_LOCK_STALE_MS) {
if (ageMs > DEFAULT_RUNTIME_LANE_GIT_MIRROR_PRESYNC_LOCK_STALE_MS) {
try {
rmSync(lockDir, { recursive: true, force: true });
continue;
@@ -395,7 +395,7 @@ export function acquireV02GitMirrorPreSyncLock(sourceCommit: string, waitMs: num
}
}
export function releaseV02GitMirrorPreSyncLock(lockDir: string): void {
export function releaseDefaultRuntimeLaneGitMirrorPreSyncLock(lockDir: string): void {
try {
rmSync(lockDir, { recursive: true, force: true });
} catch {
@@ -403,7 +403,7 @@ export function releaseV02GitMirrorPreSyncLock(lockDir: string): void {
}
}
export function waitForV02GitMirrorPreSync(sourceCommit: string, waitMs: number): Record<string, unknown> {
export function waitForDefaultRuntimeLaneGitMirrorPreSync(sourceCommit: string, waitMs: number): Record<string, unknown> {
const startedAtMs = Date.now();
const observations: Record<string, unknown>[] = [];
let attempt = 0;
@@ -425,8 +425,8 @@ export function waitForV02GitMirrorPreSync(sourceCommit: string, waitMs: number)
statusElapsedMs: Date.now() - statusStartMs,
ok: summary.ok,
required: summary.required,
localV02: summary.localV02,
githubV02: summary.githubV02,
localDefaultRuntimeLane: summary.localDefaultRuntimeLane,
githubDefaultRuntimeLane: summary.githubDefaultRuntimeLane,
pendingFlush: summary.pendingFlush,
reason: summary.reason,
});
@@ -438,8 +438,8 @@ export function waitForV02GitMirrorPreSync(sourceCommit: string, waitMs: number)
elapsedMs: Date.now() - startedAtMs,
statusElapsedMs: Date.now() - statusStartMs,
required: summary.required,
localV02: summary.localV02,
githubV02: summary.githubV02,
localDefaultRuntimeLane: summary.localDefaultRuntimeLane,
githubDefaultRuntimeLane: summary.githubDefaultRuntimeLane,
pendingFlush: summary.pendingFlush,
reason: summary.reason,
});
@@ -465,7 +465,7 @@ export function waitForV02GitMirrorPreSync(sourceCommit: string, waitMs: number)
};
}
const remainingMs = waitMs - (Date.now() - startedAtMs);
const sleepMs = Math.max(500, Math.min(V02_GIT_MIRROR_PRESYNC_POLL_MS, remainingMs));
const sleepMs = Math.max(500, Math.min(DEFAULT_RUNTIME_LANE_GIT_MIRROR_PRESYNC_POLL_MS, remainingMs));
const wait = runCommand(["sleep", String(Math.ceil(sleepMs / 1000))], repoRoot, { timeoutMs: sleepMs + 2_000 });
if (wait.exitCode !== 0 && wait.timedOut) {
return {
@@ -515,14 +515,14 @@ export function gitMirrorCacheProbeScript(): string {
" refs['local' + normalized + 'Gitops'] = localGitops;",
" refs['github' + normalized + 'Gitops'] = githubGitops;",
" if (spec.lane === 'v02') {",
" refs.localV02 = localSource;",
" refs.githubV02 = githubSource;",
" refs.localDefaultRuntimeLane = localSource;",
" refs.githubDefaultRuntimeLane = githubSource;",
" refs.localGitops = localGitops;",
" refs.githubGitops = githubGitops;",
" }",
"}",
"refs.localG14 = rev('refs/heads/G14');",
"refs.githubG14 = rev('refs/mirror-stage/heads/G14');",
"refs.localLegacyRuntime = rev('refs/heads/G14');",
"refs.githubLegacyRuntime = rev('refs/mirror-stage/heads/G14');",
"const pendingFlush = runtimeLanes.some((spec) => {",
" const localGitops = rev('refs/heads/' + spec.gitopsBranch);",
" const githubGitops = rev('refs/mirror-stage/heads/' + spec.gitopsBranch);",
@@ -533,8 +533,8 @@ export function gitMirrorCacheProbeScript(): string {
].join("\n");
}
export function preSyncV02GitMirror(sourceCommit: string, options: Pick<G14ControlPlaneOptions, "dryRun" | "timeoutSeconds">): Record<string, unknown> {
const waitMs = v02GitMirrorPreSyncWaitMs(options.timeoutSeconds);
export function preSyncDefaultRuntimeLaneGitMirror(sourceCommit: string, options: Pick<LegacyRuntimeControlPlaneOptions, "dryRun" | "timeoutSeconds">): Record<string, unknown> {
const waitMs = defaultRuntimeLaneGitMirrorPreSyncWaitMs(options.timeoutSeconds);
const statusStartMs = Date.now();
printProgressEvent("hwlab.v02.trigger.progress", { stage: "git-mirror-pre-sync-status", status: "started", sourceCommit });
const before = runGitMirrorStatus();
@@ -545,7 +545,7 @@ export function preSyncV02GitMirror(sourceCommit: string, options: Pick<G14Contr
sourceCommit,
durationMs: Date.now() - statusStartMs,
required: beforeSummary.required,
localV02: beforeSummary.localV02,
localDefaultRuntimeLane: beforeSummary.localDefaultRuntimeLane,
pendingFlush: beforeSummary.pendingFlush,
reason: beforeSummary.reason,
});
@@ -567,7 +567,7 @@ export function preSyncV02GitMirror(sourceCommit: string, options: Pick<G14Contr
before: beforeSummary,
};
}
const existingMarker = readV02GitMirrorPreSyncMarker(sourceCommit, Date.now(), statusStartMs);
const existingMarker = readDefaultRuntimeLaneGitMirrorPreSyncMarker(sourceCommit, Date.now(), statusStartMs);
if (existingMarker !== null) {
return {
ok: true,
@@ -578,9 +578,9 @@ export function preSyncV02GitMirror(sourceCommit: string, options: Pick<G14Contr
waitMs,
};
}
const lock = acquireV02GitMirrorPreSyncLock(sourceCommit, waitMs, statusStartMs);
const lock = acquireDefaultRuntimeLaneGitMirrorPreSyncLock(sourceCommit, waitMs, statusStartMs);
if (!lock.acquired) {
const markerAfterLockTimeout = lock.marker ?? readV02GitMirrorPreSyncMarker(sourceCommit, Date.now(), statusStartMs);
const markerAfterLockTimeout = lock.marker ?? readDefaultRuntimeLaneGitMirrorPreSyncMarker(sourceCommit, Date.now(), statusStartMs);
if (markerAfterLockTimeout !== null) {
return {
ok: true,
@@ -604,7 +604,7 @@ export function preSyncV02GitMirror(sourceCommit: string, options: Pick<G14Contr
};
}
try {
const markerAfterWait = readV02GitMirrorPreSyncMarker(sourceCommit, Date.now(), statusStartMs);
const markerAfterWait = readDefaultRuntimeLaneGitMirrorPreSyncMarker(sourceCommit, Date.now(), statusStartMs);
if (markerAfterWait !== null) {
return {
ok: true,
@@ -631,13 +631,13 @@ export function preSyncV02GitMirror(sourceCommit: string, options: Pick<G14Contr
sourceCommit,
durationMs: Date.now() - recheckStartMs,
required: recheckSummary.required,
localV02: recheckSummary.localV02,
githubV02: recheckSummary.githubV02,
localDefaultRuntimeLane: recheckSummary.localDefaultRuntimeLane,
githubDefaultRuntimeLane: recheckSummary.githubDefaultRuntimeLane,
pendingFlush: recheckSummary.pendingFlush,
reason: recheckSummary.reason,
});
if (recheckSummary.ok === true && recheckSummary.required === false) {
const marker = writeV02GitMirrorPreSyncMarker(sourceCommit, recheckSummary);
const marker = writeDefaultRuntimeLaneGitMirrorPreSyncMarker(sourceCommit, recheckSummary);
return {
ok: true,
mode: "became-current-after-lock-wait",
@@ -654,7 +654,7 @@ export function preSyncV02GitMirror(sourceCommit: string, options: Pick<G14Contr
status: "started",
sourceCommit,
reason: recheckSummary.reason,
localV02: recheckSummary.localV02,
localDefaultRuntimeLane: recheckSummary.localDefaultRuntimeLane,
pendingFlush: recheckSummary.pendingFlush,
});
const sync = runGitMirrorSync({
@@ -675,11 +675,11 @@ export function preSyncV02GitMirror(sourceCommit: string, options: Pick<G14Contr
const syncStatus = record(sync.status);
const after = syncStatus.ok === true ? syncStatus : runGitMirrorStatus();
const afterSummary = compactGitMirrorStatus(after, sourceCommit);
const wait = sync.ok === true && afterSummary.required === true ? waitForV02GitMirrorPreSync(sourceCommit, waitMs) : null;
const wait = sync.ok === true && afterSummary.required === true ? waitForDefaultRuntimeLaneGitMirrorPreSync(sourceCommit, waitMs) : null;
const waitFinal = wait === null ? null : record(record(wait).final);
const finalSummary = wait !== null && wait.ok === true ? waitFinal : afterSummary;
const ok = sync.ok === true && finalSummary.required === false;
const marker = ok ? writeV02GitMirrorPreSyncMarker(sourceCommit, finalSummary) : null;
const marker = ok ? writeDefaultRuntimeLaneGitMirrorPreSyncMarker(sourceCommit, finalSummary) : null;
return {
ok,
mode: "auto-sync-before-trigger",
@@ -696,7 +696,7 @@ export function preSyncV02GitMirror(sourceCommit: string, options: Pick<G14Contr
degradedReason: ok ? undefined : stringOrNull(record(wait).degradedReason) ?? "git-mirror-local-v02-not-current-after-sync",
};
} finally {
releaseV02GitMirrorPreSyncLock(lock.lockDir);
releaseDefaultRuntimeLaneGitMirrorPreSyncLock(lock.lockDir);
}
}
@@ -741,7 +741,7 @@ export function runGitMirrorStatus(lane: HwlabRuntimeLane = "v02"): Record<strin
shellQuote(gitMirrorCacheProbeScript()),
].join(" "),
].join("\n");
const bundle = g14K3s(["sh", "--", script], 60_000);
const bundle = legacyRuntimeK3s(["sh", "--", script], 60_000);
const sections = parseShellSections(statusText(bundle));
const resources = sections.resources;
const legacyCronJob = sections.legacyCronJob;
@@ -753,8 +753,8 @@ export function runGitMirrorStatus(lane: HwlabRuntimeLane = "v02"): Record<strin
command: `hwlab g14 git-mirror status --lane ${lane}`,
lane,
namespace: GIT_MIRROR_NAMESPACE,
readUrl: V02_GIT_READ_URL,
writeUrl: V02_GIT_WRITE_URL,
readUrl: DEFAULT_RUNTIME_LANE_GIT_READ_URL,
writeUrl: DEFAULT_RUNTIME_LANE_GIT_WRITE_URL,
elapsedMs: Date.now() - startedAtMs,
resources: {
ok: shellSectionOk(resources),
@@ -786,7 +786,7 @@ export function runGitMirrorStatus(lane: HwlabRuntimeLane = "v02"): Record<strin
};
}
export function runGitMirrorApply(options: G14GitMirrorOptions): Record<string, unknown> {
export function runGitMirrorApply(options: LegacyRuntimeGitMirrorOptions): Record<string, unknown> {
const spec = hwlabRuntimeLaneSpec(options.lane);
const head = resolveRuntimeLaneHead(spec);
const sourceCommit = head.sourceCommit;
@@ -835,7 +835,7 @@ export function runGitMirrorApply(options: G14GitMirrorOptions): Record<string,
};
}
export function runGitMirrorSync(options: G14GitMirrorOptions): Record<string, unknown> {
export function runGitMirrorSync(options: LegacyRuntimeGitMirrorOptions): Record<string, unknown> {
const startedAtMs = Date.now();
const spec = hwlabRuntimeLaneSpec(options.lane);
const jobName = gitMirrorSyncJobName();
@@ -872,7 +872,7 @@ export function runGitMirrorSync(options: G14GitMirrorOptions): Record<string, u
`kubectl exec -n ${shellQuote(GIT_MIRROR_NAMESPACE)} deploy/git-mirror-http -- sh -lc ${shellQuote(`cat /cache/HWLAB.last-sync.json 2>/dev/null || true; printf "\\n"; git --git-dir=/cache/pikasTech/HWLAB.git rev-parse refs/heads/${spec.sourceBranch} refs/mirror-stage/heads/${spec.sourceBranch} refs/heads/${spec.gitopsBranch} refs/mirror-stage/heads/${spec.gitopsBranch} 2>/dev/null || true`)}`,
].join("\n"),
].join("\n");
const result = g14K3s(["sh", "--", script], options.timeoutSeconds * 1000 + 30_000);
const result = legacyRuntimeK3s(["sh", "--", script], options.timeoutSeconds * 1000 + 30_000);
const syncCommand = spec.lane === "v02"
? "bun scripts/cli.ts hwlab g14 git-mirror sync --lane v02 --confirm"
: `bun scripts/cli.ts hwlab nodes git-mirror sync --node ${spec.nodeId} --lane ${spec.lane} --confirm`;
@@ -893,7 +893,7 @@ export function runGitMirrorSync(options: G14GitMirrorOptions): Record<string, u
};
}
export function runGitMirrorFlush(options: G14GitMirrorOptions): Record<string, unknown> {
export function runGitMirrorFlush(options: LegacyRuntimeGitMirrorOptions): Record<string, unknown> {
const jobName = gitMirrorFlushJobName();
const manifest = gitMirrorFlushJobManifest(jobName);
const manifestB64 = Buffer.from(JSON.stringify(manifest), "utf8").toString("base64");
@@ -928,7 +928,7 @@ export function runGitMirrorFlush(options: G14GitMirrorOptions): Record<string,
`kubectl exec -n ${shellQuote(GIT_MIRROR_NAMESPACE)} deploy/git-mirror-http -- sh -lc ${shellQuote(`cat /cache/HWLAB.last-flush.json 2>/dev/null || true; printf "\\n"; git --git-dir=/cache/pikasTech/HWLAB.git rev-parse refs/heads/${options.lane === "v02" ? "v0.2-gitops" : hwlabRuntimeLaneSpec(options.lane).gitopsBranch} refs/mirror-stage/heads/${options.lane === "v02" ? "v0.2-gitops" : hwlabRuntimeLaneSpec(options.lane).gitopsBranch} 2>/dev/null || true`)}`,
].join("\n"),
].join("\n");
const result = g14K3s(["sh", "--", script], options.timeoutSeconds * 1000 + 30_000);
const result = legacyRuntimeK3s(["sh", "--", script], options.timeoutSeconds * 1000 + 30_000);
return {
ok: isCommandSuccess(result),
command: "hwlab g14 git-mirror flush",
@@ -944,7 +944,7 @@ export function runGitMirrorFlush(options: G14GitMirrorOptions): Record<string,
};
}
export function runG14GitMirror(options: G14GitMirrorOptions): Record<string, unknown> {
export function runLegacyRuntimeGitMirror(options: LegacyRuntimeGitMirrorOptions): Record<string, unknown> {
if (options.action === "status") return runGitMirrorStatus(options.lane);
if (options.action === "apply") return runGitMirrorApply(options);
if (options.action === "flush") return runGitMirrorFlush(options);
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. help module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. help module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:10421-10618 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:10421-10618 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -10,13 +10,13 @@ import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import type { G14MonitorOptions } from "./types";
import { hwlabG14MonitorStateFileName, hwlabG14MonitorStateRole } from "./options";
import type { LegacyRuntimeMonitorOptions } from "./types";
import { hwlabLegacyRuntimeMonitorStateFileName, hwlabLegacyRuntimeMonitorStateRole } from "./options";
import { monitorBaseBranch, record } from "./remote";
import { legacyG14RetirementBlocksMonitor, legacyG14RetirementStatePath } from "./retirement";
import { DEFAULT_INTERVAL_SECONDS, DEV_APP, G14_BRIEF_INDEX_ISSUE, G14_CI_TOOLS_IMAGE_REPO, G14_OBSERVABILITY_NAMESPACE, G14_PROMETHEUS_OPERATOR_VERSION, G14_PROMETHEUS_VERSION, G14_PROVIDER, G14_SOURCE_BRANCH, G14_WORKSPACE, HWLAB_REPO, PROD_APP, V02_APP, V02_OBSERVABILITY_EXPECTED_TARGET_COUNT, V02_SOURCE_BRANCH, V02_WORKSPACE } from "./types";
import { legacyLegacyRuntimeRetirementBlocksMonitor, legacyLegacyRuntimeRetirementStatePath } from "./retirement";
import { DEFAULT_INTERVAL_SECONDS, DEV_APP, LEGACY_RUNTIME_BRIEF_INDEX_ISSUE, LEGACY_RUNTIME_CI_TOOLS_IMAGE_REPO, LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE, LEGACY_RUNTIME_PROMETHEUS_OPERATOR_VERSION, LEGACY_RUNTIME_PROMETHEUS_VERSION, LEGACY_RUNTIME_PROVIDER, LEGACY_RUNTIME_SOURCE_BRANCH, LEGACY_RUNTIME_WORKSPACE, HWLAB_REPO, PROD_APP, DEFAULT_RUNTIME_LANE_APP, DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT, DEFAULT_RUNTIME_LANE_SOURCE_BRANCH, DEFAULT_RUNTIME_LANE_WORKSPACE } from "./types";
export function hwlabG14Help(): Record<string, unknown> {
export function hwlabLegacyRuntimeHelp(): Record<string, unknown> {
return {
command: "hwlab g14",
output: "json",
@@ -88,9 +88,9 @@ export function hwlabG14Help(): Record<string, unknown> {
"bun scripts/cli.ts hwlab g14 observability status",
"bun scripts/cli.ts hwlab g14 observability apply --dry-run",
"bun scripts/cli.ts hwlab g14 observability apply --confirm",
`bun scripts/cli.ts hwlab g14 observability query --promql 'up{namespace=\"hwlab-v02\"}' --expect-count ${V02_OBSERVABILITY_EXPECTED_TARGET_COUNT} --expect-value 1`,
`bun scripts/cli.ts hwlab g14 observability query --promql 'hwlab_service_up{namespace=\"hwlab-v02\"}' --expect-count ${V02_OBSERVABILITY_EXPECTED_TARGET_COUNT} --expect-value 1`,
`bun scripts/cli.ts hwlab g14 observability query --promql 'hwlab_service_health_probe_success{namespace=\"hwlab-v02\"}' --expect-count ${V02_OBSERVABILITY_EXPECTED_TARGET_COUNT} --expect-value 1`,
`bun scripts/cli.ts hwlab g14 observability query --promql 'up{namespace=\"hwlab-v02\"}' --expect-count ${DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT} --expect-value 1`,
`bun scripts/cli.ts hwlab g14 observability query --promql 'hwlab_service_up{namespace=\"hwlab-v02\"}' --expect-count ${DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT} --expect-value 1`,
`bun scripts/cli.ts hwlab g14 observability query --promql 'hwlab_service_health_probe_success{namespace=\"hwlab-v02\"}' --expect-count ${DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT} --expect-value 1`,
"bun scripts/cli.ts hwlab g14 observability targets --lane v02",
"bun scripts/cli.ts hwlab g14 observability boundary --lane v02",
"bun scripts/cli.ts hwlab g14 observability closeout --lane v02",
@@ -104,40 +104,40 @@ export function hwlabG14Help(): Record<string, unknown> {
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. `--lane v03` monitors base=v0.3 PRs through the runtime lane control-plane, waits for GitHub preflight/CI readiness, auto-merges ready non-conflicting PRs, triggers v0.3 CD, verifies PipelineRun/Argo/runtime public probes/Git mirror flush, posts deduplicated PR comments, and creates or updates failure issues for failed checks, conflicts, merge blockers, CD failures, and timeouts. 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, cloud-api DB, master admin API key, and code-agent provider refs; 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,
legacyBase: G14_SOURCE_BRANCH,
v02Base: V02_SOURCE_BRANCH,
legacyBase: LEGACY_RUNTIME_SOURCE_BRANCH,
defaultRuntimeLaneBase: DEFAULT_RUNTIME_LANE_SOURCE_BRANCH,
runtimeLaneConfig: hwlabRuntimeLaneConfigPath(),
runtimeLanes: hwlabRuntimeLaneIds(),
provider: G14_PROVIDER,
legacyWorkspace: G14_WORKSPACE,
v02Workspace: V02_WORKSPACE,
provider: LEGACY_RUNTIME_PROVIDER,
legacyWorkspace: LEGACY_RUNTIME_WORKSPACE,
defaultRuntimeLaneWorkspace: DEFAULT_RUNTIME_LANE_WORKSPACE,
v03Workspace: hwlabRuntimeLaneSpec("v03").workspace,
ciToolsImageRepo: G14_CI_TOOLS_IMAGE_REPO,
ciToolsImageRepo: LEGACY_RUNTIME_CI_TOOLS_IMAGE_REPO,
intervalSeconds: DEFAULT_INTERVAL_SECONDS,
devApplication: DEV_APP,
prodApplication: PROD_APP,
legacyRetirementState: legacyG14RetirementStatePath(),
v02Application: V02_APP,
legacyRetirementState: legacyLegacyRuntimeRetirementStatePath(),
defaultRuntimeLaneApplication: DEFAULT_RUNTIME_LANE_APP,
v03Application: hwlabRuntimeLaneSpec("v03").app,
v03Node: hwlabRuntimeLaneSpec("v03").nodeId,
v03NetworkProfile: hwlabRuntimeLaneSpec("v03").networkProfileId,
v03DownloadProfile: hwlabRuntimeLaneSpec("v03").downloadProfileId,
requiredNoProxy: hwlabRequiredNoProxyEntries(),
briefIndexIssue: G14_BRIEF_INDEX_ISSUE,
observabilityNamespace: G14_OBSERVABILITY_NAMESPACE,
prometheusOperatorVersion: G14_PROMETHEUS_OPERATOR_VERSION,
prometheusVersion: G14_PROMETHEUS_VERSION,
briefIndexIssue: LEGACY_RUNTIME_BRIEF_INDEX_ISSUE,
observabilityNamespace: LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE,
prometheusOperatorVersion: LEGACY_RUNTIME_PROMETHEUS_OPERATOR_VERSION,
prometheusVersion: LEGACY_RUNTIME_PROMETHEUS_VERSION,
},
stateFiles: {
monitor: ".state/hwlab-g14/latest-monitor-job.json",
once: ".state/hwlab-g14/latest-once-job.json",
dryRun: ".state/hwlab-g14/latest-dry-run-job.json",
onceDryRun: ".state/hwlab-g14/latest-once-dry-run-job.json",
v02Monitor: ".state/hwlab-g14/latest-v02-monitor-job.json",
v02Once: ".state/hwlab-g14/latest-v02-once-job.json",
v02DryRun: ".state/hwlab-g14/latest-v02-dry-run-job.json",
v02OnceDryRun: ".state/hwlab-g14/latest-v02-once-dry-run-job.json",
v02PrCommentSignatures: ".state/hwlab-g14/v02-pr-comment-signatures.json",
defaultRuntimeLaneMonitor: ".state/hwlab-g14/latest-v02-monitor-job.json",
defaultRuntimeLaneOnce: ".state/hwlab-g14/latest-v02-once-job.json",
defaultRuntimeLaneDryRun: ".state/hwlab-g14/latest-v02-dry-run-job.json",
defaultRuntimeLaneOnceDryRun: ".state/hwlab-g14/latest-v02-once-dry-run-job.json",
defaultRuntimeLanePrCommentSignatures: ".state/hwlab-g14/v02-pr-comment-signatures.json",
v03Monitor: ".state/hwlab-g14/latest-v03-monitor-job.json",
v03Once: ".state/hwlab-g14/latest-v03-once-job.json",
v03DryRun: ".state/hwlab-g14/latest-v03-dry-run-job.json",
@@ -147,12 +147,12 @@ export function hwlabG14Help(): Record<string, unknown> {
};
}
export function monitorStatus(options: G14MonitorOptions): Record<string, unknown> {
export function monitorStatus(options: LegacyRuntimeMonitorOptions): Record<string, unknown> {
const stateDir = rootPath(".state", "hwlab-g14");
const stateFileName = hwlabG14MonitorStateFileName(options);
const stateFileRole = hwlabG14MonitorStateRole(options);
const stateFileName = hwlabLegacyRuntimeMonitorStateFileName(options);
const stateFileRole = hwlabLegacyRuntimeMonitorStateRole(options);
const latestPath = join(stateDir, stateFileName);
const retirement = options.lane === "g14" ? legacyG14RetirementBlocksMonitor() : null;
const retirement = options.lane === "g14" ? legacyLegacyRuntimeRetirementBlocksMonitor() : null;
const exists = existsSync(latestPath);
let latest: Record<string, unknown> | null = null;
let job: Record<string, unknown> | null = null;
@@ -204,7 +204,7 @@ export function monitorStatus(options: G14MonitorOptions): Record<string, unknow
retirement,
next: retirement !== null ? {
retirementStatus: "bun scripts/cli.ts hwlab g14 retirement status",
v02Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02",
defaultRuntimeLaneMonitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02",
v03Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v03",
} : statusCommand === null ? {
start: `bun scripts/cli.ts hwlab g14 monitor-prs --lane ${options.lane}`,
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. images module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. images module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:8075-8386 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:8075-8386 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -10,12 +10,12 @@ import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import type { CommandJsonResult, G14ControlPlaneOptions, G14GitMirrorOptions, G14ToolsImageOptions, G14UpstreamImageOptions } from "./types";
import type { CommandJsonResult, LegacyRuntimeControlPlaneOptions, LegacyRuntimeGitMirrorOptions, LegacyRuntimeToolsImageOptions, LegacyRuntimeUpstreamImageOptions } from "./types";
import { statusText } from "./pr-monitor";
import { cliJson, isCommandSuccess, record, shellQuote } from "./remote";
import { G14_CI_TOOLS_IMAGE_REPO, G14_PROVIDER, GIT_MIRROR_NAMESPACE, V02_REGISTRY_PREFIX, V02_WORKSPACE } from "./types";
import { LEGACY_RUNTIME_CI_TOOLS_IMAGE_REPO, LEGACY_RUNTIME_PROVIDER, GIT_MIRROR_NAMESPACE, DEFAULT_RUNTIME_LANE_REGISTRY_PREFIX, DEFAULT_RUNTIME_LANE_WORKSPACE } from "./types";
export function startAsyncHwlabG14Job(name: string, command: string[], note: string): Record<string, unknown> {
export function startAsyncHwlabLegacyRuntimeJob(name: string, command: string[], note: string): Record<string, unknown> {
const job = startJob(name, command, note);
const statusCommand = `bun scripts/cli.ts job status ${job.id} --tail-bytes 12000`;
return {
@@ -31,7 +31,7 @@ export function startAsyncHwlabG14Job(name: string, command: string[], note: str
};
}
export function startControlPlaneTriggerJob(options: G14ControlPlaneOptions): Record<string, unknown> {
export function startControlPlaneTriggerJob(options: LegacyRuntimeControlPlaneOptions): Record<string, unknown> {
const command = [
"bun",
"scripts/cli.ts",
@@ -52,7 +52,7 @@ export function startControlPlaneTriggerJob(options: G14ControlPlaneOptions): Re
lane: options.lane,
reason: "confirmed trigger can spend tens of seconds syncing git mirror and creating PipelineRun; default is fire-and-forget to avoid silent blocking",
waitCommand: command.join(" "),
...startAsyncHwlabG14Job(
...startAsyncHwlabLegacyRuntimeJob(
`hwlab_g14_${options.lane}_trigger_current`,
command,
`Trigger HWLAB ${options.lane} current commit PipelineRun with git mirror pre-sync through G14 control-plane`,
@@ -60,7 +60,7 @@ export function startControlPlaneTriggerJob(options: G14ControlPlaneOptions): Re
};
}
export function startGitMirrorJob(options: G14GitMirrorOptions): Record<string, unknown> {
export function startGitMirrorJob(options: LegacyRuntimeGitMirrorOptions): Record<string, unknown> {
const command = [
"bun",
"scripts/cli.ts",
@@ -81,7 +81,7 @@ export function startGitMirrorJob(options: G14GitMirrorOptions): Record<string,
namespace: GIT_MIRROR_NAMESPACE,
reason: "manual git mirror sync/flush waits for a Kubernetes Job; default is fire-and-forget to keep CLI output immediately visible",
waitCommand: command.join(" "),
...startAsyncHwlabG14Job(
...startAsyncHwlabLegacyRuntimeJob(
`hwlab_g14_git_mirror_${options.action}`,
command,
`Run HWLAB devops-infra git mirror ${options.action} through a bounded manual Kubernetes Job`,
@@ -89,16 +89,16 @@ export function startGitMirrorJob(options: G14GitMirrorOptions): Record<string,
};
}
export function g14HostScript(script: string, timeoutMs = 120_000): CommandJsonResult {
return cliJson(["ssh", G14_PROVIDER, "sh", "--", script], timeoutMs);
export function legacyRuntimeHostScript(script: string, timeoutMs = 120_000): CommandJsonResult {
return cliJson(["ssh", LEGACY_RUNTIME_PROVIDER, "sh", "--", script], timeoutMs);
}
export function g14CiToolsImage(tag: string): string {
return `${G14_CI_TOOLS_IMAGE_REPO}:${tag}`;
export function legacyRuntimeCiToolsImage(tag: string): string {
return `${LEGACY_RUNTIME_CI_TOOLS_IMAGE_REPO}:${tag}`;
}
export function runG14ToolsImageStatus(options: G14ToolsImageOptions): Record<string, unknown> {
const image = g14CiToolsImage(options.tag);
export function runLegacyRuntimeToolsImageStatus(options: LegacyRuntimeToolsImageOptions): Record<string, unknown> {
const image = legacyRuntimeCiToolsImage(options.tag);
const script = [
"set -u",
`image=${shellQuote(image)}`,
@@ -149,7 +149,7 @@ export function runG14ToolsImageStatus(options: G14ToolsImageOptions): Record<st
"console.log(JSON.stringify(payload, null, 2));",
"NODE",
].join("\n");
const result = g14HostScript(script, 180_000);
const result = legacyRuntimeHostScript(script, 180_000);
let parsedStatus: unknown = null;
const text = statusText(result);
if (text.length > 0) {
@@ -169,11 +169,11 @@ export function runG14ToolsImageStatus(options: G14ToolsImageOptions): Record<st
};
}
export function runG14ToolsImageBuild(options: G14ToolsImageOptions): Record<string, unknown> {
const image = g14CiToolsImage(options.tag);
export function runLegacyRuntimeToolsImageBuild(options: LegacyRuntimeToolsImageOptions): Record<string, unknown> {
const image = legacyRuntimeCiToolsImage(options.tag);
const script = [
"set -eu",
`cd ${shellQuote(V02_WORKSPACE)}`,
`cd ${shellQuote(DEFAULT_RUNTIME_LANE_WORKSPACE)}`,
"git fetch origin v0.2 --prune",
"git checkout v0.2 >/dev/null 2>&1 || true",
"git merge --ff-only origin/v0.2",
@@ -196,42 +196,42 @@ export function runG14ToolsImageBuild(options: G14ToolsImageOptions): Record<str
command: "hwlab g14 tools-image build --name ci-node-tools",
mode: "dry-run",
image,
workspace: V02_WORKSPACE,
workspace: DEFAULT_RUNTIME_LANE_WORKSPACE,
dockerfile: options.dockerfile,
buildScriptPreview: script.split(/\n/u),
next: { build: `bun scripts/cli.ts hwlab g14 tools-image build --name ci-node-tools --tag ${options.tag} --confirm` },
};
}
const result = g14HostScript(script, options.timeoutSeconds * 1000);
const result = legacyRuntimeHostScript(script, options.timeoutSeconds * 1000);
return {
ok: isCommandSuccess(result),
command: "hwlab g14 tools-image build --name ci-node-tools",
mode: "confirmed-build",
image,
workspace: V02_WORKSPACE,
workspace: DEFAULT_RUNTIME_LANE_WORKSPACE,
dockerfile: options.dockerfile,
result,
status: runG14ToolsImageStatus({ ...options, action: "status", dryRun: true, confirm: false }),
status: runLegacyRuntimeToolsImageStatus({ ...options, action: "status", dryRun: true, confirm: false }),
};
}
export function runG14ToolsImage(options: G14ToolsImageOptions): Record<string, unknown> {
if (options.action === "status") return runG14ToolsImageStatus(options);
return runG14ToolsImageBuild(options);
export function runLegacyRuntimeToolsImage(options: LegacyRuntimeToolsImageOptions): Record<string, unknown> {
if (options.action === "status") return runLegacyRuntimeToolsImageStatus(options);
return runLegacyRuntimeToolsImageBuild(options);
}
export function g14UpstreamImageTarget(options: G14UpstreamImageOptions): string {
return `${V02_REGISTRY_PREFIX}/${options.name}:${options.tag}`;
export function legacyRuntimeUpstreamImageTarget(options: LegacyRuntimeUpstreamImageOptions): string {
return `${DEFAULT_RUNTIME_LANE_REGISTRY_PREFIX}/${options.name}:${options.tag}`;
}
export function g14UpstreamImageSource(options: G14UpstreamImageOptions): string {
export function legacyRuntimeUpstreamImageSource(options: LegacyRuntimeUpstreamImageOptions): string {
if (options.name === "openfga") return `docker.io/openfga/openfga:${options.tag}`;
return `${options.name}:${options.tag}`;
}
export function runG14UpstreamImageStatus(options: G14UpstreamImageOptions): Record<string, unknown> {
const sourceImage = g14UpstreamImageSource(options);
const targetImage = g14UpstreamImageTarget(options);
export function runLegacyRuntimeUpstreamImageStatus(options: LegacyRuntimeUpstreamImageOptions): Record<string, unknown> {
const sourceImage = legacyRuntimeUpstreamImageSource(options);
const targetImage = legacyRuntimeUpstreamImageTarget(options);
const script = [
"set +e",
`source_image=${shellQuote(sourceImage)}`,
@@ -257,7 +257,7 @@ export function runG14UpstreamImageStatus(options: G14UpstreamImageOptions): Rec
"}, null, 2));",
"NODE",
].join("\n");
const result = g14HostScript(script, 120_000);
const result = legacyRuntimeHostScript(script, 120_000);
let parsedStatus: unknown = null;
const text = statusText(result);
if (text.length > 0) {
@@ -279,9 +279,9 @@ export function runG14UpstreamImageStatus(options: G14UpstreamImageOptions): Rec
};
}
export function runG14UpstreamImageEnsure(options: G14UpstreamImageOptions): Record<string, unknown> {
const sourceImage = g14UpstreamImageSource(options);
const targetImage = g14UpstreamImageTarget(options);
export function runLegacyRuntimeUpstreamImageEnsure(options: LegacyRuntimeUpstreamImageOptions): Record<string, unknown> {
const sourceImage = legacyRuntimeUpstreamImageSource(options);
const targetImage = legacyRuntimeUpstreamImageTarget(options);
if (options.dryRun) {
return {
ok: true,
@@ -310,8 +310,8 @@ export function runG14UpstreamImageEnsure(options: G14UpstreamImageOptions): Rec
"digest=$(docker image inspect \"$target_image\" --format '{{index .RepoDigests 0}}' 2>/dev/null || true)",
"echo \"{\\\"phase\\\":\\\"published\\\",\\\"targetImage\\\":\\\"$target_image\\\",\\\"digest\\\":\\\"$digest\\\"}\"",
].join("\n");
const result = g14HostScript(script, options.timeoutSeconds * 1000);
const status = runG14UpstreamImageStatus(options);
const result = legacyRuntimeHostScript(script, options.timeoutSeconds * 1000);
const status = runLegacyRuntimeUpstreamImageStatus(options);
return {
ok: isCommandSuccess(result) && status.ok === true,
command: "hwlab g14 upstream-image ensure --name openfga",
@@ -326,7 +326,7 @@ export function runG14UpstreamImageEnsure(options: G14UpstreamImageOptions): Rec
};
}
export function runG14UpstreamImage(options: G14UpstreamImageOptions): Record<string, unknown> {
if (options.action === "status") return runG14UpstreamImageStatus(options);
return runG14UpstreamImageEnsure(options);
export function runLegacyRuntimeUpstreamImage(options: LegacyRuntimeUpstreamImageOptions): Record<string, unknown> {
if (options.action === "status") return runLegacyRuntimeUpstreamImageStatus(options);
return runLegacyRuntimeUpstreamImageEnsure(options);
}
@@ -1,9 +1,9 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. Domain barrel for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. Domain barrel for scripts/src/hwlab-legacy-runtime.ts.
export * from "./types";
export * from "./options";
export * from "./remote";
export * from "./source";
export * from "./v02-status";
export * from "./runtime-status";
export * from "./cleanup";
export * from "./render";
export * from "./control-plane";
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. monitor module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. monitor module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:10163-10420 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:10163-10420 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -10,14 +10,14 @@ import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import type { CommandJsonResult, G14MonitorOptions } from "./types";
import { runRuntimeLanePrAutoCd, runV02PrAutoCd } from "./cd-trigger";
import { appendRolloutBrief, collectPipelineMetrics, commentRuntimeLanePullRequest, commentV02PullRequest, durationSeconds, getArgoStatus, getDevWorkloads, getG14Head, getLiveHealth, getPipelineStatus, listOpenG14PullRequests, mergePullRequest, preflightPullRequest, preflightSummary, refreshArgoDev, reportRuntimeLaneAutomationIssue, statusText, v02PrConflictState, workloadReadiness } from "./pr-monitor";
import { commandData, compactCommandResult, extractPullRequests, isCommandSuccess, nested, precheckWorkspace, printEvent, printRuntimeLanePrMonitorProgress, printV02PrMonitorProgress, record, shortSha, sleep } from "./remote";
import { legacyG14RetirementBlocksMonitor } from "./retirement";
import { V02_SOURCE_BRANCH } from "./types";
import type { CommandJsonResult, LegacyRuntimeMonitorOptions } from "./types";
import { runRuntimeLanePrAutoCd, runDefaultRuntimeLanePrAutoCd } from "./cd-trigger";
import { appendRolloutBrief, collectPipelineMetrics, commentRuntimeLanePullRequest, commentDefaultRuntimeLanePullRequest, durationSeconds, getArgoStatus, getDevWorkloads, getLegacyRuntimeHead, getLiveHealth, getPipelineStatus, listOpenLegacyRuntimePullRequests, mergePullRequest, preflightPullRequest, preflightSummary, refreshArgoDev, reportRuntimeLaneAutomationIssue, statusText, defaultRuntimeLanePrConflictState, workloadReadiness } from "./pr-monitor";
import { commandData, compactCommandResult, extractPullRequests, isCommandSuccess, nested, precheckWorkspace, printEvent, printRuntimeLanePrMonitorProgress, printDefaultRuntimeLanePrMonitorProgress, record, shortSha, sleep } from "./remote";
import { legacyLegacyRuntimeRetirementBlocksMonitor } from "./retirement";
import { DEFAULT_RUNTIME_LANE_SOURCE_BRANCH } from "./types";
export async function waitForG14Dev(sourceCommit: string, timeoutSeconds: number): Promise<Record<string, unknown>> {
export async function waitForLegacyRuntimeDev(sourceCommit: string, timeoutSeconds: number): Promise<Record<string, unknown>> {
const started = Date.now();
const startedAt = new Date(started).toISOString();
let pipelineText = "";
@@ -59,15 +59,15 @@ export async function waitForG14Dev(sourceCommit: string, timeoutSeconds: number
return { ok: false, phase: "timeout", sourceCommit, pipelineText, argoText, startedAt, pipelineSucceededAt, workloadsReady, healthOk, timeoutSeconds };
}
export async function monitorV02Cycle(options: G14MonitorOptions, cycle: number): Promise<Record<string, unknown>> {
export async function monitorDefaultRuntimeLaneCycle(options: LegacyRuntimeMonitorOptions, cycle: number): Promise<Record<string, unknown>> {
printEvent("v02.monitor.cycle.start", { cycle, dryRun: options.dryRun });
printV02PrMonitorProgress({ stage: "cycle", status: "started", cycle, dryRun: options.dryRun });
const listed = listOpenG14PullRequests();
printDefaultRuntimeLanePrMonitorProgress({ stage: "cycle", status: "started", cycle, dryRun: options.dryRun });
const listed = listOpenLegacyRuntimePullRequests();
if (!isCommandSuccess(listed)) return { ok: false, cycle, phase: "list-prs", listed };
const prs = extractPullRequests(listed, V02_SOURCE_BRANCH);
const prs = extractPullRequests(listed, DEFAULT_RUNTIME_LANE_SOURCE_BRANCH);
printEvent("v02.monitor.prs", { cycle, count: prs.length, pullRequests: prs });
if (prs.length === 0) {
printV02PrMonitorProgress({ stage: "idle", status: "waiting", cycle, pullRequests: 0, intervalSeconds: options.intervalSeconds });
printDefaultRuntimeLanePrMonitorProgress({ stage: "idle", status: "waiting", cycle, pullRequests: 0, intervalSeconds: options.intervalSeconds });
return { ok: true, cycle, action: "none", lane: "v02", pullRequests: [] };
}
const observations: unknown[] = [];
@@ -81,19 +81,19 @@ export async function monitorV02Cycle(options: G14MonitorOptions, cycle: number)
ok: isCommandSuccess(preflightResult),
conclusion: preflight.conclusion,
readyForCommanderMerge: preflight.readyForCommanderMerge,
conflict: v02PrConflictState(preflight),
conflict: defaultRuntimeLanePrConflictState(preflight),
});
printV02PrMonitorProgress({
printDefaultRuntimeLanePrMonitorProgress({
stage: "preflight",
status: preflight.readyForCommanderMerge === true ? "succeeded" : "running",
pr: pr.number,
conclusion: preflight.conclusion,
conflict: v02PrConflictState(preflight),
conflict: defaultRuntimeLanePrConflictState(preflight),
});
if (!isCommandSuccess(preflightResult) || preflight.readyForCommanderMerge !== true) {
const conclusion = String(preflight.conclusion ?? "unknown");
const blocked = conclusion === "blocked" || v02PrConflictState(preflight) === "conflict" || !isCommandSuccess(preflightResult);
const comment = commentV02PullRequest({
const blocked = conclusion === "blocked" || defaultRuntimeLanePrConflictState(preflight) === "conflict" || !isCommandSuccess(preflightResult);
const comment = commentDefaultRuntimeLanePullRequest({
pr,
phase: "preflight",
state: blocked ? "blocked" : "waiting-ci",
@@ -107,14 +107,14 @@ export async function monitorV02Cycle(options: G14MonitorOptions, cycle: number)
: "v0.2 自动化正在等待 GitHub CI / mergeability 收敛;CI 通过且无冲突后会自动合并并触发 CD。",
});
observations.push({ pullRequest: pr, preflight, comment });
printV02PrMonitorProgress({ stage: "pr-comment", status: record(comment).ok === true ? "succeeded" : "failed", pr: pr.number, conclusion: preflight.conclusion });
printDefaultRuntimeLanePrMonitorProgress({ stage: "pr-comment", status: record(comment).ok === true ? "succeeded" : "failed", pr: pr.number, conclusion: preflight.conclusion });
if (record(comment).ok !== true) return { ok: false, cycle, lane: "v02", phase: "pr-comment", pullRequest: pr, preflight, comment, observations };
continue;
}
const merge = mergePullRequest(pr.number, options.dryRun);
printEvent("v02.pr.merge", { cycle, number: pr.number, dryRun: options.dryRun, ok: isCommandSuccess(merge) });
printV02PrMonitorProgress({ stage: "merge", status: isCommandSuccess(merge) ? "succeeded" : "running", pr: pr.number, dryRun: options.dryRun });
const result = await runV02PrAutoCd(pr, preflight, merge, options, startedAt);
printDefaultRuntimeLanePrMonitorProgress({ stage: "merge", status: isCommandSuccess(merge) ? "succeeded" : "running", pr: pr.number, dryRun: options.dryRun });
const result = await runDefaultRuntimeLanePrAutoCd(pr, preflight, merge, options, startedAt);
observations.push(result);
if (record(result).ok !== true) return { ok: false, cycle, lane: "v02", phase: record(result).phase ?? "v02-auto-cd", pullRequest: pr, result, observations };
return { ok: true, cycle, lane: "v02", action: record(result).action ?? (options.dryRun ? "dry-run-merge" : "merged-and-rolled-v02"), result, observations };
@@ -127,7 +127,7 @@ export function runtimeLanePreflightBlocked(preflightResult: CommandJsonResult,
const pending = Array.isArray(preflight.pending) ? preflight.pending : [];
const conclusion = String(preflight.conclusion ?? "unknown").toLowerCase();
return !isCommandSuccess(preflightResult)
|| v02PrConflictState(preflight) === "conflict"
|| defaultRuntimeLanePrConflictState(preflight) === "conflict"
|| conclusion === "blocked"
|| conclusion === "failed"
|| conclusion === "failure"
@@ -135,10 +135,10 @@ export function runtimeLanePreflightBlocked(preflightResult: CommandJsonResult,
|| (blockers.length > 0 && pending.length === 0);
}
export async function monitorRuntimeLaneCycle(spec: HwlabRuntimeLaneSpec, options: G14MonitorOptions, cycle: number): Promise<Record<string, unknown>> {
export async function monitorRuntimeLaneCycle(spec: HwlabRuntimeLaneSpec, options: LegacyRuntimeMonitorOptions, cycle: number): Promise<Record<string, unknown>> {
printEvent(`${spec.lane}.monitor.cycle.start`, { cycle, dryRun: options.dryRun });
printRuntimeLanePrMonitorProgress(spec, { stage: "cycle", status: "started", cycle, dryRun: options.dryRun });
const listed = listOpenG14PullRequests();
const listed = listOpenLegacyRuntimePullRequests();
if (!isCommandSuccess(listed)) return { ok: false, cycle, lane: spec.lane, phase: "list-prs", listed };
const prs = extractPullRequests(listed, spec.sourceBranch);
printEvent(`${spec.lane}.monitor.prs`, { cycle, count: prs.length, pullRequests: prs });
@@ -157,14 +157,14 @@ export async function monitorRuntimeLaneCycle(spec: HwlabRuntimeLaneSpec, option
ok: isCommandSuccess(preflightResult),
conclusion: preflight.conclusion,
readyForCommanderMerge: preflight.readyForCommanderMerge,
conflict: v02PrConflictState(preflight),
conflict: defaultRuntimeLanePrConflictState(preflight),
});
printRuntimeLanePrMonitorProgress(spec, {
stage: "preflight",
status: preflight.readyForCommanderMerge === true ? "succeeded" : "running",
pr: pr.number,
conclusion: preflight.conclusion,
conflict: v02PrConflictState(preflight),
conflict: defaultRuntimeLanePrConflictState(preflight),
});
if (!isCommandSuccess(preflightResult) || preflight.readyForCommanderMerge !== true) {
const blocked = runtimeLanePreflightBlocked(preflightResult, preflight);
@@ -211,10 +211,10 @@ export async function monitorRuntimeLaneCycle(spec: HwlabRuntimeLaneSpec, option
return { ok: true, cycle, lane: spec.lane, action: "none", observations };
}
export async function monitorCycle(options: G14MonitorOptions, cycle: number): Promise<Record<string, unknown>> {
if (options.lane === "v02") return monitorV02Cycle(options, cycle);
export async function monitorCycle(options: LegacyRuntimeMonitorOptions, cycle: number): Promise<Record<string, unknown>> {
if (options.lane === "v02") return monitorDefaultRuntimeLaneCycle(options, cycle);
if (options.lane !== "g14") return monitorRuntimeLaneCycle(hwlabRuntimeLaneSpec(options.lane), options, cycle);
const retirement = legacyG14RetirementBlocksMonitor();
const retirement = legacyLegacyRuntimeRetirementBlocksMonitor();
if (retirement !== null) {
return {
ok: false,
@@ -222,13 +222,13 @@ export async function monitorCycle(options: G14MonitorOptions, cycle: number): P
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" },
next: { status: "bun scripts/cli.ts hwlab g14 retirement status", defaultRuntimeLaneMonitor: "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 };
const listed = listOpenG14PullRequests();
const listed = listOpenLegacyRuntimePullRequests();
if (!isCommandSuccess(listed)) return { ok: false, cycle, phase: "list-prs", listed };
const prs = extractPullRequests(listed);
printEvent("g14.monitor.prs", { cycle, count: prs.length, pullRequests: prs });
@@ -243,8 +243,8 @@ export async function monitorCycle(options: G14MonitorOptions, cycle: number): P
const merge = mergePullRequest(pr.number, options.dryRun);
printEvent("g14.pr.merge", { cycle, number: pr.number, dryRun: options.dryRun, ok: isCommandSuccess(merge) });
if (!isCommandSuccess(merge)) return { ok: false, cycle, phase: "pr-merge", pullRequest: pr, merge };
const sourceCommit = getG14Head();
const rollout = options.dryRun || sourceCommit === null ? { skipped: true, dryRun: options.dryRun, sourceCommit } : await waitForG14Dev(sourceCommit, options.timeoutSeconds);
const sourceCommit = getLegacyRuntimeHead();
const rollout = options.dryRun || sourceCommit === null ? { skipped: true, dryRun: options.dryRun, sourceCommit } : await waitForLegacyRuntimeDev(sourceCommit, options.timeoutSeconds);
const brief = options.dryRun || sourceCommit === null || record(rollout).ok !== true
? { skipped: true, dryRun: options.dryRun }
: appendRolloutBrief({ prNumber: pr.number, sourceCommit, dryRun: false }, rollout);
@@ -256,7 +256,7 @@ export async function monitorCycle(options: G14MonitorOptions, cycle: number): P
return { ok: true, cycle, action: options.dryRun ? "dry-run-merge" : "merged-and-rolled-dev", merged };
}
export async function runMonitorWorker(options: G14MonitorOptions): Promise<Record<string, unknown>> {
export async function runMonitorWorker(options: LegacyRuntimeMonitorOptions): Promise<Record<string, unknown>> {
const maxCycles = options.once ? 1 : options.maxCycles;
let cycle = 0;
const results: unknown[] = [];
@@ -268,7 +268,7 @@ export async function runMonitorWorker(options: G14MonitorOptions): Promise<Reco
if (record(result).ok !== true) return { ok: false, cycles: cycle, lastResult: result, results };
if (options.once || (options.lane === "g14" && record(result).action !== "none")) return { ok: true, cycles: cycle, lastResult: result, results };
printEvent(`${options.lane}.monitor.sleep`, { cycle, lane: options.lane, intervalSeconds: options.intervalSeconds });
if (options.lane === "v02") printV02PrMonitorProgress({ stage: "idle", status: "waiting", cycle, intervalSeconds: options.intervalSeconds });
if (options.lane === "v02") printDefaultRuntimeLanePrMonitorProgress({ stage: "idle", status: "waiting", cycle, intervalSeconds: options.intervalSeconds });
else if (options.lane !== "g14") printRuntimeLanePrMonitorProgress(hwlabRuntimeLaneSpec(options.lane), { stage: "idle", status: "waiting", cycle, intervalSeconds: options.intervalSeconds });
await sleep(options.intervalSeconds * 1000);
}
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. observability module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. observability module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:6778-8074 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:6778-8074 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -10,12 +10,12 @@ import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import type { CommandJsonResult, G14ObservabilityOptions, ShellSection } from "./types";
import type { CommandJsonResult, LegacyRuntimeObservabilityOptions, ShellSection } from "./types";
import { commandErrorSummary, tailText } from "./cleanup";
import { statusText } from "./pr-monitor";
import { commandJson, compactCommandMetadata, compactCommandResult, g14K3s, isCommandSuccess, nested, record, shellQuote, stringOrNull } from "./remote";
import { G14_OBSERVABILITY_FIELD_MANAGER, G14_OBSERVABILITY_NAMESPACE, G14_PROMETHEUS_NAME, G14_PROMETHEUS_OPERATOR_RELEASE_ASSET, G14_PROMETHEUS_OPERATOR_VERSION, G14_PROMETHEUS_SERVICE, G14_PROMETHEUS_SERVICE_ACCOUNT, G14_PROMETHEUS_VERSION, V02_CLOUD_API_URL, V02_CLOUD_WEB_URL, V02_OBSERVABILITY_BOOLEAN_QUERY_NAMES, V02_OBSERVABILITY_CLOSEOUT_QUERY_NAMES, V02_OBSERVABILITY_EXPECTED_TARGET_COUNT, V02_OBSERVABILITY_QUERIES, V02_OBSERVABILITY_SERVICE_IDS, V02_RUNTIME_NAMESPACE } from "./types";
import { numericValue, parseShellSections, shellSectionOk } from "./v02-status";
import { commandJson, compactCommandMetadata, compactCommandResult, legacyRuntimeK3s, isCommandSuccess, nested, record, shellQuote, stringOrNull } from "./remote";
import { LEGACY_RUNTIME_OBSERVABILITY_FIELD_MANAGER, LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE, LEGACY_RUNTIME_PROMETHEUS_NAME, LEGACY_RUNTIME_PROMETHEUS_OPERATOR_RELEASE_ASSET, LEGACY_RUNTIME_PROMETHEUS_OPERATOR_VERSION, LEGACY_RUNTIME_PROMETHEUS_SERVICE, LEGACY_RUNTIME_PROMETHEUS_SERVICE_ACCOUNT, LEGACY_RUNTIME_PROMETHEUS_VERSION, DEFAULT_RUNTIME_LANE_CLOUD_API_URL, DEFAULT_RUNTIME_LANE_CLOUD_WEB_URL, DEFAULT_RUNTIME_LANE_OBSERVABILITY_BOOLEAN_QUERY_NAMES, DEFAULT_RUNTIME_LANE_OBSERVABILITY_CLOSEOUT_QUERY_NAMES, DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT, DEFAULT_RUNTIME_LANE_OBSERVABILITY_QUERIES, DEFAULT_RUNTIME_LANE_OBSERVABILITY_SERVICE_IDS, DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE } from "./types";
import { numericValue, parseShellSections, shellSectionOk } from "./runtime-status";
export function observabilityLabels(component: string): Record<string, string> {
return {
@@ -31,7 +31,7 @@ export function observabilityNamespaceLabel(): Record<string, string> {
};
}
export function g14PrometheusManifest(): Record<string, unknown> {
export function legacyRuntimePrometheusManifest(): Record<string, unknown> {
const namespaceSelector = { matchLabels: observabilityNamespaceLabel() };
const monitorSelector = { matchLabels: { "hwlab.pikastech.local/monitoring": "enabled" } };
return {
@@ -42,7 +42,7 @@ export function g14PrometheusManifest(): Record<string, unknown> {
apiVersion: "v1",
kind: "Namespace",
metadata: {
name: G14_OBSERVABILITY_NAMESPACE,
name: LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE,
labels: {
...observabilityNamespaceLabel(),
...observabilityLabels("namespace"),
@@ -53,7 +53,7 @@ export function g14PrometheusManifest(): Record<string, unknown> {
apiVersion: "v1",
kind: "Namespace",
metadata: {
name: V02_RUNTIME_NAMESPACE,
name: DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE,
labels: observabilityNamespaceLabel(),
},
},
@@ -61,8 +61,8 @@ export function g14PrometheusManifest(): Record<string, unknown> {
apiVersion: "v1",
kind: "ServiceAccount",
metadata: {
name: G14_PROMETHEUS_SERVICE_ACCOUNT,
namespace: G14_OBSERVABILITY_NAMESPACE,
name: LEGACY_RUNTIME_PROMETHEUS_SERVICE_ACCOUNT,
namespace: LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE,
labels: observabilityLabels("prometheus"),
},
},
@@ -70,7 +70,7 @@ export function g14PrometheusManifest(): Record<string, unknown> {
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "ClusterRole",
metadata: {
name: G14_PROMETHEUS_SERVICE_ACCOUNT,
name: LEGACY_RUNTIME_PROMETHEUS_SERVICE_ACCOUNT,
labels: observabilityLabels("prometheus"),
},
rules: [
@@ -99,32 +99,32 @@ export function g14PrometheusManifest(): Record<string, unknown> {
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "ClusterRoleBinding",
metadata: {
name: G14_PROMETHEUS_SERVICE_ACCOUNT,
name: LEGACY_RUNTIME_PROMETHEUS_SERVICE_ACCOUNT,
labels: observabilityLabels("prometheus"),
},
roleRef: {
apiGroup: "rbac.authorization.k8s.io",
kind: "ClusterRole",
name: G14_PROMETHEUS_SERVICE_ACCOUNT,
name: LEGACY_RUNTIME_PROMETHEUS_SERVICE_ACCOUNT,
},
subjects: [{
kind: "ServiceAccount",
name: G14_PROMETHEUS_SERVICE_ACCOUNT,
namespace: G14_OBSERVABILITY_NAMESPACE,
name: LEGACY_RUNTIME_PROMETHEUS_SERVICE_ACCOUNT,
namespace: LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE,
}],
},
{
apiVersion: "monitoring.coreos.com/v1",
kind: "Prometheus",
metadata: {
name: G14_PROMETHEUS_NAME,
namespace: G14_OBSERVABILITY_NAMESPACE,
name: LEGACY_RUNTIME_PROMETHEUS_NAME,
namespace: LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE,
labels: observabilityLabels("prometheus"),
},
spec: {
replicas: 1,
version: G14_PROMETHEUS_VERSION,
serviceAccountName: G14_PROMETHEUS_SERVICE_ACCOUNT,
version: LEGACY_RUNTIME_PROMETHEUS_VERSION,
serviceAccountName: LEGACY_RUNTIME_PROMETHEUS_SERVICE_ACCOUNT,
scrapeInterval: "30s",
evaluationInterval: "30s",
retention: "7d",
@@ -155,14 +155,14 @@ export function g14PrometheusManifest(): Record<string, unknown> {
apiVersion: "v1",
kind: "Service",
metadata: {
name: G14_PROMETHEUS_SERVICE,
namespace: G14_OBSERVABILITY_NAMESPACE,
name: LEGACY_RUNTIME_PROMETHEUS_SERVICE,
namespace: LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE,
labels: observabilityLabels("query"),
},
spec: {
type: "ClusterIP",
selector: {
prometheus: G14_PROMETHEUS_NAME,
prometheus: LEGACY_RUNTIME_PROMETHEUS_NAME,
},
ports: [{
name: "web",
@@ -240,7 +240,7 @@ export function prometheusReady(prometheus: Record<string, unknown>): boolean {
}
export function observabilityQueryPath(promql: string): string {
return `/api/v1/namespaces/${G14_OBSERVABILITY_NAMESPACE}/services/http:${G14_PROMETHEUS_SERVICE}:9090/proxy/api/v1/query?query=${encodeURIComponent(promql)}`;
return `/api/v1/namespaces/${LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE}/services/http:${LEGACY_RUNTIME_PROMETHEUS_SERVICE}:9090/proxy/api/v1/query?query=${encodeURIComponent(promql)}`;
}
export function arrayRecords(value: unknown): Record<string, unknown>[] {
@@ -339,7 +339,7 @@ export function metricValueMatches(actual: string | null, expected: string): boo
return Number.isFinite(actualNumber) && Number.isFinite(expectedNumber) && actualNumber === expectedNumber;
}
export function g14ObservabilityQueryAssertion(parsed: unknown, expectedCount?: number, expectedValue?: string): Record<string, unknown> | null {
export function legacyRuntimeObservabilityQueryAssertion(parsed: unknown, expectedCount?: number, expectedValue?: string): Record<string, unknown> | null {
if (expectedCount === undefined && expectedValue === undefined) return null;
const series = prometheusResultItems(parsed);
const actualCount = series.length;
@@ -368,9 +368,9 @@ export function g14ObservabilityQueryAssertion(parsed: unknown, expectedCount?:
}
export function observabilitySemanticName(promql: string): string | null {
if (promql === V02_OBSERVABILITY_QUERIES.scrapeReachable) return "scrapeReachable";
if (promql === V02_OBSERVABILITY_QUERIES.sidecarServing) return "sidecarServing";
if (promql === V02_OBSERVABILITY_QUERIES.businessHealthProbe) return "businessHealthProbe";
if (promql === DEFAULT_RUNTIME_LANE_OBSERVABILITY_QUERIES.scrapeReachable) return "scrapeReachable";
if (promql === DEFAULT_RUNTIME_LANE_OBSERVABILITY_QUERIES.sidecarServing) return "sidecarServing";
if (promql === DEFAULT_RUNTIME_LANE_OBSERVABILITY_QUERIES.businessHealthProbe) return "businessHealthProbe";
return null;
}
@@ -546,7 +546,7 @@ export function buildObservabilityLevelSummary(targets: Record<string, unknown>[
const scrapeDurations = observedTargets.map((target) => targetMetricValue(target, "scrapeDurationSeconds")).filter((item): item is number => item !== null);
const samplesScraped = observedTargets.map((target) => targetMetricValue(target, "scrapeSamplesScraped")).filter((item): item is number => item !== null);
const resourceSnapshots = observedTargets.map((target) => record(target.resource)).filter((item) => Object.keys(item).length > 0);
const resourceVisible = resourceSnapshots.length === V02_OBSERVABILITY_EXPECTED_TARGET_COUNT;
const resourceVisible = resourceSnapshots.length === DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT;
const resourceValue = (snapshot: Record<string, unknown>, path: string[]): number | null => numericValue(nested(snapshot, path));
const resourceTotals = {
totalCpuMillicores: resourceSnapshots.map((item) => resourceValue(item, ["total", "cpuMillicores"])).filter((item): item is number => item !== null),
@@ -584,7 +584,7 @@ export function buildObservabilityLevelSummary(targets: Record<string, unknown>[
];
return {
observedTargetCount: observedTargets.length,
expectedTargetCount: V02_OBSERVABILITY_EXPECTED_TARGET_COUNT,
expectedTargetCount: DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT,
healthProbeDuration: secondsStats(healthDurations),
scrapeDuration: secondsStats(scrapeDurations),
scrapeSamplesScraped: {
@@ -596,7 +596,7 @@ export function buildObservabilityLevelSummary(targets: Record<string, unknown>[
resourceUsage: {
source: "metrics.k8s.io/v1beta1",
observedTargetCount: resourceSnapshots.length,
expectedTargetCount: V02_OBSERVABILITY_EXPECTED_TARGET_COUNT,
expectedTargetCount: DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT,
totalCpuMillicores: numberStats(resourceTotals.totalCpuMillicores),
totalMemoryMiB: numberStats(resourceTotals.totalMemoryMiB),
businessCpuMillicores: numberStats(resourceTotals.businessCpuMillicores),
@@ -756,9 +756,9 @@ export function looksLikePrometheusText(body: string, contentType: string): bool
return type.includes("text/plain") || type.length === 0;
}
export function g14ObservabilityStatus(): Record<string, unknown> {
export function legacyRuntimeObservabilityStatus(): Record<string, unknown> {
const startedAtMs = Date.now();
const queryPath = `/api/v1/namespaces/${G14_OBSERVABILITY_NAMESPACE}/services/http:${G14_PROMETHEUS_SERVICE}:9090/proxy/api/v1/query?query=${encodeURIComponent("up")}`;
const queryPath = `/api/v1/namespaces/${LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE}/services/http:${LEGACY_RUNTIME_PROMETHEUS_SERVICE}:9090/proxy/api/v1/query?query=${encodeURIComponent("up")}`;
const crds = [
"servicemonitors.monitoring.coreos.com",
"podmonitors.monitoring.coreos.com",
@@ -776,18 +776,18 @@ export function g14ObservabilityStatus(): Record<string, unknown> {
" code=$?",
" printf '\\n__UNIDESK_SECTION_END__ %s exit=%s\\n' \"$name\" \"$code\"",
"}",
`section namespace kubectl get namespace ${shellQuote(G14_OBSERVABILITY_NAMESPACE)} -o json`,
`section discoveryNamespace kubectl get namespace ${shellQuote(V02_RUNTIME_NAMESPACE)} -o json`,
`section namespace kubectl get namespace ${shellQuote(LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE)} -o json`,
`section discoveryNamespace kubectl get namespace ${shellQuote(DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE)} -o json`,
`section crds kubectl get crd ${crds.map(shellQuote).join(" ")} -o 'jsonpath={range .items[*]}{.metadata.name}{"\\n"}{end}'`,
`section operator kubectl get deploy -n ${shellQuote(G14_OBSERVABILITY_NAMESPACE)} prometheus-operator -o json`,
`section operatorPods kubectl get pods -n ${shellQuote(G14_OBSERVABILITY_NAMESPACE)} -l app.kubernetes.io/name=prometheus-operator -o 'jsonpath={range .items[*]}{.metadata.name}{"\\t"}{.status.phase}{"\\t"}{range .status.containerStatuses[*]}{.ready}{","}{end}{"\\n"}{end}'`,
`section prometheus kubectl get prometheus -n ${shellQuote(G14_OBSERVABILITY_NAMESPACE)} ${shellQuote(G14_PROMETHEUS_NAME)} -o json`,
`section prometheusPods kubectl get pods -n ${shellQuote(G14_OBSERVABILITY_NAMESPACE)} -l prometheus=${shellQuote(G14_PROMETHEUS_NAME)} -o 'jsonpath={range .items[*]}{.metadata.name}{"\\t"}{.status.phase}{"\\t"}{range .status.containerStatuses[*]}{.ready}{","}{end}{"\\n"}{end}'`,
`section prometheusService kubectl get service -n ${shellQuote(G14_OBSERVABILITY_NAMESPACE)} ${shellQuote(G14_PROMETHEUS_SERVICE)} -o json`,
`section workloadMonitors kubectl get servicemonitor,prometheusrule -n ${shellQuote(V02_RUNTIME_NAMESPACE)} -l hwlab.pikastech.local/monitoring=enabled -o json`,
`section operator kubectl get deploy -n ${shellQuote(LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE)} prometheus-operator -o json`,
`section operatorPods kubectl get pods -n ${shellQuote(LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE)} -l app.kubernetes.io/name=prometheus-operator -o 'jsonpath={range .items[*]}{.metadata.name}{"\\t"}{.status.phase}{"\\t"}{range .status.containerStatuses[*]}{.ready}{","}{end}{"\\n"}{end}'`,
`section prometheus kubectl get prometheus -n ${shellQuote(LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE)} ${shellQuote(LEGACY_RUNTIME_PROMETHEUS_NAME)} -o json`,
`section prometheusPods kubectl get pods -n ${shellQuote(LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE)} -l prometheus=${shellQuote(LEGACY_RUNTIME_PROMETHEUS_NAME)} -o 'jsonpath={range .items[*]}{.metadata.name}{"\\t"}{.status.phase}{"\\t"}{range .status.containerStatuses[*]}{.ready}{","}{end}{"\\n"}{end}'`,
`section prometheusService kubectl get service -n ${shellQuote(LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE)} ${shellQuote(LEGACY_RUNTIME_PROMETHEUS_SERVICE)} -o json`,
`section workloadMonitors kubectl get servicemonitor,prometheusrule -n ${shellQuote(DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE)} -l hwlab.pikastech.local/monitoring=enabled -o json`,
`section query kubectl get --raw ${shellQuote(queryPath)}`,
].join("\n");
const bundle = g14K3s(["sh", "--", script], 120_000);
const bundle = legacyRuntimeK3s(["sh", "--", script], 120_000);
const sections = parseShellSections(statusText(bundle));
const namespace = parseSectionJson(sections.namespace);
const discoveryNamespace = parseSectionJson(sections.discoveryNamespace);
@@ -813,17 +813,17 @@ export function g14ObservabilityStatus(): Record<string, unknown> {
return {
ok,
command: "hwlab g14 observability status",
namespace: G14_OBSERVABILITY_NAMESPACE,
namespace: LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE,
mode: "status",
elapsedMs: Date.now() - startedAtMs,
versions: {
prometheusOperator: G14_PROMETHEUS_OPERATOR_VERSION,
prometheus: G14_PROMETHEUS_VERSION,
operatorBundle: G14_PROMETHEUS_OPERATOR_RELEASE_ASSET,
prometheusOperator: LEGACY_RUNTIME_PROMETHEUS_OPERATOR_VERSION,
prometheus: LEGACY_RUNTIME_PROMETHEUS_VERSION,
operatorBundle: LEGACY_RUNTIME_PROMETHEUS_OPERATOR_RELEASE_ASSET,
},
discovery: {
namespaceLabel,
workloadNamespace: V02_RUNTIME_NAMESPACE,
workloadNamespace: DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE,
workloadNamespaceLabel,
selectorLabel: "hwlab.pikastech.local/monitoring=enabled",
},
@@ -849,8 +849,8 @@ export function g14ObservabilityStatus(): Record<string, unknown> {
},
prometheus: {
ok: prometheusExists && prometheusIsReady,
name: G14_PROMETHEUS_NAME,
service: G14_PROMETHEUS_SERVICE,
name: LEGACY_RUNTIME_PROMETHEUS_NAME,
service: LEGACY_RUNTIME_PROMETHEUS_SERVICE,
serviceExists: Object.keys(prometheusService).length > 0,
ready: prometheusIsReady,
conditions: Array.isArray(record(prometheus.status).conditions) ? record(prometheus.status).conditions : [],
@@ -863,7 +863,7 @@ export function g14ObservabilityStatus(): Record<string, unknown> {
},
workloadMonitors: {
ok: shellSectionOk(sections.workloadMonitors),
namespace: V02_RUNTIME_NAMESPACE,
namespace: DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE,
count: workloadMonitorItems.length,
items: workloadMonitorItems.map((item) => ({
kind: item.kind ?? null,
@@ -888,7 +888,7 @@ export function g14ObservabilityStatus(): Record<string, unknown> {
};
}
export function g14ObservabilityApplyScript(options: G14ObservabilityOptions, manifestB64: string): string {
export function legacyRuntimeObservabilityApplyScript(options: LegacyRuntimeObservabilityOptions, manifestB64: string): string {
const dryRunArg = options.dryRun ? "--dry-run=server" : "";
const stackDryRunCommand = options.dryRun
? [
@@ -904,22 +904,22 @@ export function g14ObservabilityApplyScript(options: G14ObservabilityOptions, ma
"kubectl apply --dry-run=client --validate=false -f \"$core_stack_path\"",
"echo prometheus_cr_dry_run=skipped_until_monitoring_crds_are_installed",
].join("\n")
: `kubectl apply --server-side --force-conflicts --field-manager=${shellQuote(G14_OBSERVABILITY_FIELD_MANAGER)} -f "$stack_path"`;
: `kubectl apply --server-side --force-conflicts --field-manager=${shellQuote(LEGACY_RUNTIME_OBSERVABILITY_FIELD_MANAGER)} -f "$stack_path"`;
const preStackWaitCommands = options.dryRun
? "echo observability_wait=skipped_dry_run"
: [
"kubectl wait --for=condition=Established --timeout=45s crd/servicemonitors.monitoring.coreos.com crd/podmonitors.monitoring.coreos.com crd/prometheusrules.monitoring.coreos.com crd/prometheuses.monitoring.coreos.com",
`kubectl -n ${shellQuote(G14_OBSERVABILITY_NAMESPACE)} get deploy/prometheus-operator -o name`,
`kubectl -n ${shellQuote(LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE)} get deploy/prometheus-operator -o name`,
].join("\n");
const postStackWaitCommands = options.dryRun
? "echo prometheus_wait=skipped_dry_run"
: "echo prometheus_wait=deferred_to_status_command";
return [
"set -eu",
`namespace=${shellQuote(G14_OBSERVABILITY_NAMESPACE)}`,
`bundle_url=${shellQuote(G14_PROMETHEUS_OPERATOR_RELEASE_ASSET)}`,
`operator_version=${shellQuote(G14_PROMETHEUS_OPERATOR_VERSION)}`,
`prometheus_version=${shellQuote(G14_PROMETHEUS_VERSION)}`,
`namespace=${shellQuote(LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE)}`,
`bundle_url=${shellQuote(LEGACY_RUNTIME_PROMETHEUS_OPERATOR_RELEASE_ASSET)}`,
`operator_version=${shellQuote(LEGACY_RUNTIME_PROMETHEUS_OPERATOR_VERSION)}`,
`prometheus_version=${shellQuote(LEGACY_RUNTIME_PROMETHEUS_VERSION)}`,
`stack_b64=${shellQuote(manifestB64)}`,
"tmpdir=$(mktemp -d /tmp/g14-observability-XXXXXX)",
"cleanup() { rm -rf \"$tmpdir\"; }",
@@ -938,14 +938,14 @@ export function g14ObservabilityApplyScript(options: G14ObservabilityOptions, ma
"cat > \"$tmpdir/kustomization.yaml\" <<'YAML'",
"apiVersion: kustomize.config.k8s.io/v1beta1",
"kind: Kustomization",
`namespace: ${G14_OBSERVABILITY_NAMESPACE}`,
`namespace: ${LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE}`,
"resources:",
"- operator-bundle.yaml",
"YAML",
"kubectl kustomize \"$tmpdir\" > \"$operator_path\"",
"grep -q 'namespace: devops-infra' \"$operator_path\"",
`kubectl create namespace ${shellQuote(G14_OBSERVABILITY_NAMESPACE)} --dry-run=client -o yaml | kubectl apply --server-side --force-conflicts --field-manager=${shellQuote(G14_OBSERVABILITY_FIELD_MANAGER)} ${dryRunArg} -f -`,
`kubectl apply --server-side --force-conflicts --field-manager=${shellQuote(G14_OBSERVABILITY_FIELD_MANAGER)} ${dryRunArg} -f "$operator_path"`,
`kubectl create namespace ${shellQuote(LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE)} --dry-run=client -o yaml | kubectl apply --server-side --force-conflicts --field-manager=${shellQuote(LEGACY_RUNTIME_OBSERVABILITY_FIELD_MANAGER)} ${dryRunArg} -f -`,
`kubectl apply --server-side --force-conflicts --field-manager=${shellQuote(LEGACY_RUNTIME_OBSERVABILITY_FIELD_MANAGER)} ${dryRunArg} -f "$operator_path"`,
preStackWaitCommands,
stackDryRunCommand,
postStackWaitCommands,
@@ -953,36 +953,36 @@ export function g14ObservabilityApplyScript(options: G14ObservabilityOptions, ma
].join("\n");
}
export function runG14ObservabilityApply(options: G14ObservabilityOptions): Record<string, unknown> {
export function runLegacyRuntimeObservabilityApply(options: LegacyRuntimeObservabilityOptions): Record<string, unknown> {
const startedAtMs = Date.now();
const manifest = g14PrometheusManifest();
const manifest = legacyRuntimePrometheusManifest();
const manifestB64 = Buffer.from(JSON.stringify(manifest), "utf8").toString("base64");
const script = g14ObservabilityApplyScript(options, manifestB64);
const result = g14K3s(["sh", "--", script], options.timeoutSeconds * 1000 + 90_000);
const script = legacyRuntimeObservabilityApplyScript(options, manifestB64);
const result = legacyRuntimeK3s(["sh", "--", script], options.timeoutSeconds * 1000 + 90_000);
const ok = isCommandSuccess(result);
return {
ok,
command: "hwlab g14 observability apply",
mode: options.dryRun ? "dry-run" : "confirmed-apply",
namespace: G14_OBSERVABILITY_NAMESPACE,
namespace: LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE,
versions: {
prometheusOperator: G14_PROMETHEUS_OPERATOR_VERSION,
prometheus: G14_PROMETHEUS_VERSION,
operatorBundle: G14_PROMETHEUS_OPERATOR_RELEASE_ASSET,
prometheusOperator: LEGACY_RUNTIME_PROMETHEUS_OPERATOR_VERSION,
prometheus: LEGACY_RUNTIME_PROMETHEUS_VERSION,
operatorBundle: LEGACY_RUNTIME_PROMETHEUS_OPERATOR_RELEASE_ASSET,
},
manifest: options.dryRun ? manifest : undefined,
elapsedMs: Date.now() - startedAtMs,
result: compactCommandResult(result),
status: ok && !options.dryRun ? g14ObservabilityStatus() : undefined,
status: ok && !options.dryRun ? legacyRuntimeObservabilityStatus() : undefined,
next: options.dryRun
? { apply: "bun scripts/cli.ts hwlab g14 observability apply --confirm" }
: { status: "bun scripts/cli.ts hwlab g14 observability status", query: 'bun scripts/cli.ts hwlab g14 observability query --promql \'up{namespace="hwlab-v02"}\'' },
};
}
export function g14PrometheusQuery(promql: string, timeoutMs: number): { serviceProxyPath: string; result: CommandJsonResult; parsed: Record<string, unknown>; ok: boolean } {
export function legacyRuntimePrometheusQuery(promql: string, timeoutMs: number): { serviceProxyPath: string; result: CommandJsonResult; parsed: Record<string, unknown>; ok: boolean } {
const serviceProxyPath = observabilityQueryPath(promql);
const result = g14K3s(["kubectl", "get", "--raw", serviceProxyPath], timeoutMs);
const result = legacyRuntimeK3s(["kubectl", "get", "--raw", serviceProxyPath], timeoutMs);
const parsed = (() => {
try {
return record(JSON.parse(statusText(result)) as unknown);
@@ -993,9 +993,9 @@ export function g14PrometheusQuery(promql: string, timeoutMs: number): { service
return { serviceProxyPath, result, parsed, ok: isCommandSuccess(result) && parsed.status === "success" };
}
export function runG14ObservabilityQuery(options: G14ObservabilityOptions): Record<string, unknown> {
const query = g14PrometheusQuery(options.query, options.timeoutSeconds * 1000);
const assertion = g14ObservabilityQueryAssertion(query.parsed, options.expectCount, options.expectValue);
export function runLegacyRuntimeObservabilityQuery(options: LegacyRuntimeObservabilityOptions): Record<string, unknown> {
const query = legacyRuntimePrometheusQuery(options.query, options.timeoutSeconds * 1000);
const assertion = legacyRuntimeObservabilityQueryAssertion(query.parsed, options.expectCount, options.expectValue);
const semantic = observabilitySemanticName(options.query);
const resultCount = prometheusResultItems(query.parsed).length;
const hasAssertion = options.expectCount !== undefined || options.expectValue !== undefined;
@@ -1004,8 +1004,8 @@ export function runG14ObservabilityQuery(options: G14ObservabilityOptions): Reco
return {
ok,
command: "hwlab g14 observability query",
namespace: G14_OBSERVABILITY_NAMESPACE,
service: G14_PROMETHEUS_SERVICE,
namespace: LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE,
service: LEGACY_RUNTIME_PROMETHEUS_SERVICE,
promql: options.query,
semantic,
serviceProxyPath: query.serviceProxyPath,
@@ -1031,9 +1031,9 @@ export function runG14ObservabilityQuery(options: G14ObservabilityOptions): Reco
};
}
export function runG14ObservabilityTargets(options: G14ObservabilityOptions): Record<string, unknown> {
export function runLegacyRuntimeObservabilityTargets(options: LegacyRuntimeObservabilityOptions): Record<string, unknown> {
const startedAtMs = Date.now();
const queryCommands = Object.entries(V02_OBSERVABILITY_QUERIES).map(([name, promql]) => `section query_${name} kubectl get --raw ${shellQuote(observabilityQueryPath(promql))}`);
const queryCommands = Object.entries(DEFAULT_RUNTIME_LANE_OBSERVABILITY_QUERIES).map(([name, promql]) => `section query_${name} kubectl get --raw ${shellQuote(observabilityQueryPath(promql))}`);
const script = [
"set +e",
"section() {",
@@ -1044,23 +1044,23 @@ export function runG14ObservabilityTargets(options: G14ObservabilityOptions): Re
" code=$?",
" printf '\\n__UNIDESK_SECTION_END__ %s exit=%s\\n' \"$name\" \"$code\"",
"}",
`section pods kubectl get pods -n ${shellQuote(V02_RUNTIME_NAMESPACE)} -o json`,
`section monitors kubectl get servicemonitor,podmonitor,prometheusrule -n ${shellQuote(V02_RUNTIME_NAMESPACE)} -l hwlab.pikastech.local/monitoring=enabled -o json`,
`section podResourceMetrics kubectl get --raw ${shellQuote(`/apis/metrics.k8s.io/v1beta1/namespaces/${V02_RUNTIME_NAMESPACE}/pods`)}`,
`section pods kubectl get pods -n ${shellQuote(DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE)} -o json`,
`section monitors kubectl get servicemonitor,podmonitor,prometheusrule -n ${shellQuote(DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE)} -l hwlab.pikastech.local/monitoring=enabled -o json`,
`section podResourceMetrics kubectl get --raw ${shellQuote(`/apis/metrics.k8s.io/v1beta1/namespaces/${DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE}/pods`)}`,
...queryCommands,
].join("\n");
const bundle = g14K3s(["sh", "--", script], options.timeoutSeconds * 1000);
const bundle = legacyRuntimeK3s(["sh", "--", script], options.timeoutSeconds * 1000);
const sections = parseShellSections(statusText(bundle));
const pods = k8sItems(parseSectionJson(sections.pods));
const monitors = k8sItems(parseSectionJson(sections.monitors));
const podResourceMetrics = k8sItems(parseSectionJson(sections.podResourceMetrics)).map(podResourceMetricSummary);
const parsedQueries = Object.fromEntries(Object.entries(V02_OBSERVABILITY_QUERIES).map(([name]) => [name, parseSectionJson(sections[`query_${name}`])]));
const parsedQueries = Object.fromEntries(Object.entries(DEFAULT_RUNTIME_LANE_OBSERVABILITY_QUERIES).map(([name]) => [name, parseSectionJson(sections[`query_${name}`])]));
const metricSeries = Object.fromEntries(Object.entries(parsedQueries).map(([name, parsed]) => [name, prometheusResultItems(parsed)]));
const booleanQueryNames = new Set<string>(V02_OBSERVABILITY_BOOLEAN_QUERY_NAMES);
const closeoutQueryNames = new Set<string>(V02_OBSERVABILITY_CLOSEOUT_QUERY_NAMES);
const booleanQueryNames = new Set<string>(DEFAULT_RUNTIME_LANE_OBSERVABILITY_BOOLEAN_QUERY_NAMES);
const closeoutQueryNames = new Set<string>(DEFAULT_RUNTIME_LANE_OBSERVABILITY_CLOSEOUT_QUERY_NAMES);
const assertions = Object.fromEntries(Object.entries(parsedQueries).map(([name, parsed]) => [
name,
g14ObservabilityQueryAssertion(parsed, V02_OBSERVABILITY_EXPECTED_TARGET_COUNT, booleanQueryNames.has(name) ? "1" : undefined),
legacyRuntimeObservabilityQueryAssertion(parsed, DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT, booleanQueryNames.has(name) ? "1" : undefined),
]));
const targets = attachResourceMetricsToTargets(mergeObservabilityTargets(pods, monitors, metricSeries), podResourceMetrics);
const compactTargets = targets.map(compactObservabilityTarget);
@@ -1070,24 +1070,24 @@ export function runG14ObservabilityTargets(options: G14ObservabilityOptions): Re
.filter((sidecar) => Object.keys(sidecar).length > 0);
const notReadySidecars = sidecars.filter((sidecar) => sidecar.sidecarReady !== true || sidecar.podReady !== true);
const restartProblems = sidecars.filter((sidecar) => (numericValue(sidecar.restartCount) ?? 0) > 0);
const queryOk = V02_OBSERVABILITY_CLOSEOUT_QUERY_NAMES.every((name) => record(assertions[name]).ok === true);
const sidecarOk = targets.length === V02_OBSERVABILITY_EXPECTED_TARGET_COUNT
&& sidecars.length === V02_OBSERVABILITY_EXPECTED_TARGET_COUNT
const queryOk = DEFAULT_RUNTIME_LANE_OBSERVABILITY_CLOSEOUT_QUERY_NAMES.every((name) => record(assertions[name]).ok === true);
const sidecarOk = targets.length === DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT
&& sidecars.length === DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT
&& notReadySidecars.length === 0;
const monitorsOk = shellSectionOk(sections.monitors) && monitors.length > 0;
const resourceOk = shellSectionOk(sections.podResourceMetrics)
&& podResourceMetrics.filter((item) => {
const serviceId = stringOrNull(item.serviceId);
return serviceId !== null && V02_OBSERVABILITY_SERVICE_IDS.includes(serviceId);
}).length === V02_OBSERVABILITY_EXPECTED_TARGET_COUNT;
return serviceId !== null && DEFAULT_RUNTIME_LANE_OBSERVABILITY_SERVICE_IDS.includes(serviceId);
}).length === DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT;
const ok = isCommandSuccess(bundle) && shellSectionOk(sections.pods) && monitorsOk && queryOk && sidecarOk && resourceOk;
return {
ok,
command: "hwlab g14 observability targets",
lane: options.lane,
namespace: V02_RUNTIME_NAMESPACE,
namespace: DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE,
elapsedMs: Date.now() - startedAtMs,
expectedTargetCount: V02_OBSERVABILITY_EXPECTED_TARGET_COUNT,
expectedTargetCount: DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT,
targetCount: targets.length,
levelSummary,
targets: compactTargets,
@@ -1109,14 +1109,14 @@ export function runG14ObservabilityTargets(options: G14ObservabilityOptions): Re
source: "metrics.k8s.io/v1beta1",
observedTargetCount: podResourceMetrics.filter((item) => {
const serviceId = stringOrNull(item.serviceId);
return serviceId !== null && V02_OBSERVABILITY_SERVICE_IDS.includes(serviceId);
return serviceId !== null && DEFAULT_RUNTIME_LANE_OBSERVABILITY_SERVICE_IDS.includes(serviceId);
}).length,
expectedTargetCount: V02_OBSERVABILITY_EXPECTED_TARGET_COUNT,
expectedTargetCount: DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT,
sectionOk: shellSectionOk(sections.podResourceMetrics),
stderr: shellSectionOk(sections.podResourceMetrics) ? "" : commandErrorSummary(bundle),
},
queries: Object.fromEntries(Object.entries(parsedQueries).map(([name, parsed]) => [name, {
promql: record(V02_OBSERVABILITY_QUERIES)[name],
promql: record(DEFAULT_RUNTIME_LANE_OBSERVABILITY_QUERIES)[name],
resultType: nested(parsed, ["data", "resultType"]) ?? null,
resultCount: prometheusResultItems(parsed).length,
assertion: assertions[name],
@@ -1124,7 +1124,7 @@ export function runG14ObservabilityTargets(options: G14ObservabilityOptions): Re
reason: closeoutQueryNames.has(name) ? "target-summary-uses-assertion-and-target-rows" : "auxiliary-metric-summarized-in-levelSummary",
summaryField: closeoutQueryNames.has(name) ? "targets" : "levelSummary",
drillDownCommand: closeoutQueryNames.has(name)
? `bun scripts/cli.ts hwlab g14 observability query --promql ${shellQuote(String(record(V02_OBSERVABILITY_QUERIES)[name]))} --expect-count ${V02_OBSERVABILITY_EXPECTED_TARGET_COUNT} --expect-value 1`
? `bun scripts/cli.ts hwlab g14 observability query --promql ${shellQuote(String(record(DEFAULT_RUNTIME_LANE_OBSERVABILITY_QUERIES)[name]))} --expect-count ${DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT} --expect-value 1`
: undefined,
},
sectionOk: shellSectionOk(sections[`query_${name}`]),
@@ -1135,7 +1135,7 @@ export function runG14ObservabilityTargets(options: G14ObservabilityOptions): Re
};
}
export function runG14ObservabilityBoundary(options: G14ObservabilityOptions): Record<string, unknown> {
export function runLegacyRuntimeObservabilityBoundary(options: LegacyRuntimeObservabilityOptions): Record<string, unknown> {
const startedAtMs = Date.now();
const script = [
"set +e",
@@ -1147,20 +1147,20 @@ export function runG14ObservabilityBoundary(options: G14ObservabilityOptions): R
" code=$?",
" printf '\\n__UNIDESK_SECTION_END__ %s exit=%s\\n' \"$name\" \"$code\"",
"}",
`section workloadMonitoring kubectl get servicemonitor,podmonitor,prometheusrule,prometheus,alertmanager -n ${shellQuote(V02_RUNTIME_NAMESPACE)} -o json`,
`section infraControlPlane kubectl get prometheus,alertmanager -n ${shellQuote(G14_OBSERVABILITY_NAMESPACE)} -o json`,
`section workloadMonitoring kubectl get servicemonitor,podmonitor,prometheusrule,prometheus,alertmanager -n ${shellQuote(DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE)} -o json`,
`section infraControlPlane kubectl get prometheus,alertmanager -n ${shellQuote(LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE)} -o json`,
].join("\n");
const bundle = g14K3s(["sh", "--", script], options.timeoutSeconds * 1000);
const bundle = legacyRuntimeK3s(["sh", "--", script], options.timeoutSeconds * 1000);
const sections = parseShellSections(statusText(bundle));
const workloadItems = k8sItems(parseSectionJson(sections.workloadMonitoring));
const infraItems = k8sItems(parseSectionJson(sections.infraControlPlane));
const forbiddenWorkloadControlPlane = workloadItems.filter((item) => item.kind === "Prometheus" || item.kind === "Alertmanager");
const allowedWorkloadDeclarations = workloadItems.filter((item) => item.kind === "ServiceMonitor" || item.kind === "PodMonitor" || item.kind === "PrometheusRule");
const namespaceBoundaryOk = shellSectionOk(sections.workloadMonitoring) && forbiddenWorkloadControlPlane.length === 0;
const infraControlPlaneOk = shellSectionOk(sections.infraControlPlane) && infraItems.some((item) => item.kind === "Prometheus" && k8sMetadataName(item) === G14_PROMETHEUS_NAME);
const infraControlPlaneOk = shellSectionOk(sections.infraControlPlane) && infraItems.some((item) => item.kind === "Prometheus" && k8sMetadataName(item) === LEGACY_RUNTIME_PROMETHEUS_NAME);
const publicProbes = [
publicMetricsProbe("hwlab-v02-cloud-web", `${V02_CLOUD_WEB_URL}/metrics`),
publicMetricsProbe("hwlab-v02-cloud-api", `${V02_CLOUD_API_URL}/metrics`),
publicMetricsProbe("hwlab-v02-cloud-web", `${DEFAULT_RUNTIME_LANE_CLOUD_WEB_URL}/metrics`),
publicMetricsProbe("hwlab-v02-cloud-api", `${DEFAULT_RUNTIME_LANE_CLOUD_API_URL}/metrics`),
];
const publicMetricsExposureOk = publicProbes.every((probe) => probe.ok === true && probe.exposed !== true);
const ok = isCommandSuccess(bundle) && namespaceBoundaryOk && infraControlPlaneOk && publicMetricsExposureOk;
@@ -1171,7 +1171,7 @@ export function runG14ObservabilityBoundary(options: G14ObservabilityOptions): R
elapsedMs: Date.now() - startedAtMs,
namespaceBoundary: {
ok: namespaceBoundaryOk,
workloadNamespace: V02_RUNTIME_NAMESPACE,
workloadNamespace: DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE,
allowedKinds: ["ServiceMonitor", "PodMonitor", "PrometheusRule"],
allowedDeclarationCount: allowedWorkloadDeclarations.length,
allowedDeclarations: monitorSummaries(allowedWorkloadDeclarations),
@@ -1182,7 +1182,7 @@ export function runG14ObservabilityBoundary(options: G14ObservabilityOptions): R
},
infraControlPlane: {
ok: infraControlPlaneOk,
namespace: G14_OBSERVABILITY_NAMESPACE,
namespace: LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE,
items: infraItems.map((item) => ({ kind: item.kind, name: k8sMetadataName(item) })),
sectionOk: shellSectionOk(sections.infraControlPlane),
},
@@ -1213,11 +1213,11 @@ export function closeoutAdvice(summary: Record<string, unknown>): string[] {
return advice;
}
export function runG14ObservabilityCloseout(options: G14ObservabilityOptions): Record<string, unknown> {
export function runLegacyRuntimeObservabilityCloseout(options: LegacyRuntimeObservabilityOptions): Record<string, unknown> {
const startedAtMs = Date.now();
const status = g14ObservabilityStatus();
const targets = runG14ObservabilityTargets(options);
const boundary = runG14ObservabilityBoundary(options);
const status = legacyRuntimeObservabilityStatus();
const targets = runLegacyRuntimeObservabilityTargets(options);
const boundary = runLegacyRuntimeObservabilityBoundary(options);
const targetQueries = record(targets.queries);
const queryOk = (name: string): boolean => record(record(targetQueries[name]).assertion).ok === true;
const sidecarsOk = record(record(targets.sidecars)).ok === true;
@@ -1241,7 +1241,7 @@ export function runG14ObservabilityCloseout(options: G14ObservabilityOptions): R
const ok = Object.entries(summary).every(([key, value]) => key === "workloadMonitorCount"
? numericValue(value) !== null && Number(value) > 0
: key === "observedTargetCount"
? numericValue(value) === V02_OBSERVABILITY_EXPECTED_TARGET_COUNT
? numericValue(value) === DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT
: key === "publicMetricsExposureState"
? value === "denied"
: value === "pass");
@@ -1291,9 +1291,9 @@ export function runG14ObservabilityCloseout(options: G14ObservabilityOptions): R
status: "bun scripts/cli.ts hwlab g14 observability status",
targets: "bun scripts/cli.ts hwlab g14 observability targets --lane v02",
boundary: "bun scripts/cli.ts hwlab g14 observability boundary --lane v02",
scrapeReachable: `bun scripts/cli.ts hwlab g14 observability query --promql 'up{namespace=\"hwlab-v02\"}' --expect-count ${V02_OBSERVABILITY_EXPECTED_TARGET_COUNT} --expect-value 1`,
sidecarServing: `bun scripts/cli.ts hwlab g14 observability query --promql 'hwlab_service_up{namespace=\"hwlab-v02\"}' --expect-count ${V02_OBSERVABILITY_EXPECTED_TARGET_COUNT} --expect-value 1`,
businessHealthProbe: `bun scripts/cli.ts hwlab g14 observability query --promql 'hwlab_service_health_probe_success{namespace=\"hwlab-v02\"}' --expect-count ${V02_OBSERVABILITY_EXPECTED_TARGET_COUNT} --expect-value 1`,
scrapeReachable: `bun scripts/cli.ts hwlab g14 observability query --promql 'up{namespace=\"hwlab-v02\"}' --expect-count ${DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT} --expect-value 1`,
sidecarServing: `bun scripts/cli.ts hwlab g14 observability query --promql 'hwlab_service_up{namespace=\"hwlab-v02\"}' --expect-count ${DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT} --expect-value 1`,
businessHealthProbe: `bun scripts/cli.ts hwlab g14 observability query --promql 'hwlab_service_health_probe_success{namespace=\"hwlab-v02\"}' --expect-count ${DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT} --expect-value 1`,
},
},
degradedReason: ok ? undefined : "observability-closeout-failed",
@@ -1305,11 +1305,11 @@ export function runG14ObservabilityCloseout(options: G14ObservabilityOptions): R
};
}
export function runG14Observability(options: G14ObservabilityOptions): Record<string, unknown> {
if (options.action === "status") return g14ObservabilityStatus();
if (options.action === "query") return runG14ObservabilityQuery(options);
if (options.action === "targets") return runG14ObservabilityTargets(options);
if (options.action === "boundary") return runG14ObservabilityBoundary(options);
if (options.action === "closeout") return runG14ObservabilityCloseout(options);
return runG14ObservabilityApply(options);
export function runLegacyRuntimeObservability(options: LegacyRuntimeObservabilityOptions): Record<string, unknown> {
if (options.action === "status") return legacyRuntimeObservabilityStatus();
if (options.action === "query") return runLegacyRuntimeObservabilityQuery(options);
if (options.action === "targets") return runLegacyRuntimeObservabilityTargets(options);
if (options.action === "boundary") return runLegacyRuntimeObservabilityBoundary(options);
if (options.action === "closeout") return runLegacyRuntimeObservabilityCloseout(options);
return runLegacyRuntimeObservabilityApply(options);
}
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. options module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. options module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:289-638 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:289-638 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -10,11 +10,11 @@ import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import type { G14ControlPlaneOptions, G14GitMirrorOptions, G14LegacyRetirementOptions, G14MonitorLane, G14MonitorOptions, G14ObservabilityOptions, G14RecordRolloutOptions, G14SecretOptions, G14ToolsImageOptions, G14UpstreamImageOptions } from "./types";
import type { LegacyRuntimeControlPlaneOptions, LegacyRuntimeGitMirrorOptions, LegacyRuntimeLegacyRetirementOptions, LegacyRuntimeMonitorLane, LegacyRuntimeMonitorOptions, LegacyRuntimeObservabilityOptions, LegacyRuntimeRecordRolloutOptions, LegacyRuntimeSecretOptions, LegacyRuntimeToolsImageOptions, LegacyRuntimeUpstreamImageOptions } from "./types";
import { positiveIntegerOption, runtimeLaneMasterAdminApiKeySecretName, runtimeLaneOpenFgaSecretName } from "./remote";
import { DEFAULT_INTERVAL_SECONDS, DEFAULT_MAX_CYCLES, DEFAULT_TIMEOUT_SECONDS, G14_CI_TOOLS_BASE_TAG, V02_MASTER_ADMIN_API_KEY_SECRET, V02_MASTER_ADMIN_API_KEY_SECRET_KEY, V02_OPENFGA_AUTHN_SECRET_KEY, V02_OPENFGA_DATASTORE_URI_SECRET_KEY, V02_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY, V02_OPENFGA_SECRET } from "./types";
import { DEFAULT_INTERVAL_SECONDS, DEFAULT_MAX_CYCLES, DEFAULT_TIMEOUT_SECONDS, LEGACY_RUNTIME_CI_TOOLS_BASE_TAG, DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET, DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET_KEY, DEFAULT_RUNTIME_LANE_OPENFGA_AUTHN_SECRET_KEY, DEFAULT_RUNTIME_LANE_OPENFGA_DATASTORE_URI_SECRET_KEY, DEFAULT_RUNTIME_LANE_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY, DEFAULT_RUNTIME_LANE_OPENFGA_SECRET } from "./types";
export function hwlabG14MonitorStateFileName(options: Pick<G14MonitorOptions, "once" | "dryRun"> & { lane?: G14MonitorLane }): string {
export function hwlabLegacyRuntimeMonitorStateFileName(options: Pick<LegacyRuntimeMonitorOptions, "once" | "dryRun"> & { lane?: LegacyRuntimeMonitorLane }): string {
const prefix = options.lane !== undefined && options.lane !== "g14" ? `latest-${options.lane}-` : "latest-";
if (options.once && options.dryRun) return `${prefix}once-dry-run-job.json`;
if (options.once) return `${prefix}once-job.json`;
@@ -22,7 +22,7 @@ export function hwlabG14MonitorStateFileName(options: Pick<G14MonitorOptions, "o
return `${prefix}monitor-job.json`;
}
export function hwlabG14MonitorStateRole(options: Pick<G14MonitorOptions, "once" | "dryRun"> & { lane?: G14MonitorLane }): string {
export function hwlabLegacyRuntimeMonitorStateRole(options: Pick<LegacyRuntimeMonitorOptions, "once" | "dryRun"> & { lane?: LegacyRuntimeMonitorLane }): string {
const lanePrefix = options.lane !== undefined && options.lane !== "g14" ? `${options.lane}-` : "";
if (options.once && options.dryRun) return `${lanePrefix}once-dry-run`;
if (options.once) return `${lanePrefix}once`;
@@ -30,13 +30,13 @@ export function hwlabG14MonitorStateRole(options: Pick<G14MonitorOptions, "once"
return `${lanePrefix}monitor`;
}
export function parseMonitorLane(args: string[]): G14MonitorLane {
export function parseMonitorLane(args: string[]): LegacyRuntimeMonitorLane {
const lane = optionValue(args, "--lane") ?? "g14";
if (lane !== "g14" && !isHwlabRuntimeLane(lane)) throw new Error(`monitor-prs --lane must be g14 or ${hwlabRuntimeLaneIds().join("|")}`);
return lane;
}
export function parseOptions(args: string[]): G14MonitorOptions {
export function parseOptions(args: string[]): LegacyRuntimeMonitorOptions {
return {
lane: parseMonitorLane(args),
intervalSeconds: positiveIntegerOption(args, "--interval-seconds", DEFAULT_INTERVAL_SECONDS, 86400),
@@ -61,7 +61,7 @@ export function validateFullShaOption(value: string, name: string): string {
return value.toLowerCase();
}
export function validateV02PipelineRunOption(value: string): string {
export function validateDefaultRuntimeLanePipelineRunOption(value: string): string {
if (!/^hwlab-v02-ci-poll-[0-9a-f]{7,40}$/iu.test(value)) throw new Error("--pipeline-run must be a hwlab-v02-ci-poll-<sha> PipelineRun name");
return value.toLowerCase();
}
@@ -75,7 +75,7 @@ export function validateRuntimeLanePipelineRunOption(lane: HwlabRuntimeLane, val
return value.toLowerCase();
}
export function parseRecordRolloutOptions(args: string[]): G14RecordRolloutOptions {
export function parseRecordRolloutOptions(args: string[]): LegacyRuntimeRecordRolloutOptions {
const prRaw = optionValue(args, "--pr") ?? optionValue(args, "--number");
const prNumber = Number(prRaw);
if (!Number.isInteger(prNumber) || prNumber <= 0) throw new Error("record-rollout requires --pr <number>");
@@ -91,7 +91,7 @@ export function parseRecordRolloutOptions(args: string[]): G14RecordRolloutOptio
};
}
export function parseControlPlaneOptions(args: string[]): G14ControlPlaneOptions {
export function parseControlPlaneOptions(args: string[]): LegacyRuntimeControlPlaneOptions {
const [rawAction] = args;
const actionRaw = rawAction === "rerun-current" ? "trigger-current" : rawAction;
if (
@@ -107,7 +107,7 @@ export function parseControlPlaneOptions(args: string[]): G14ControlPlaneOptions
throw new Error("control-plane usage: status|apply|trigger-current|refresh --lane v02|v03 | closeout|runtime-migration --lane v02 | cleanup-runs --lane v02|v03|g14|all | cleanup-released-pvs --lane all [--dry-run|--confirm]");
}
const laneRaw = optionValue(args, "--lane") ?? (actionRaw === "cleanup-released-pvs" ? "all" : "v02");
let lane: G14ControlPlaneOptions["lane"];
let lane: LegacyRuntimeControlPlaneOptions["lane"];
if (actionRaw === "cleanup-runs") {
if (laneRaw !== "g14" && laneRaw !== "all" && !isHwlabRuntimeLane(laneRaw)) throw new Error("control-plane cleanup-runs requires --lane v02|v03|g14|all");
lane = laneRaw;
@@ -153,12 +153,12 @@ export function parseControlPlaneOptions(args: string[]): G14ControlPlaneOptions
? undefined
: isHwlabRuntimeLane(lane)
? validateRuntimeLanePipelineRunOption(lane, pipelineRunRaw)
: validateV02PipelineRunOption(pipelineRunRaw),
: validateDefaultRuntimeLanePipelineRunOption(pipelineRunRaw),
history: args.includes("--history"),
};
}
export function parseLegacyRetirementOptions(args: string[]): G14LegacyRetirementOptions {
export function parseLegacyRetirementOptions(args: string[]): LegacyRuntimeLegacyRetirementOptions {
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]");
@@ -180,14 +180,14 @@ export function parseLegacyRetirementOptions(args: string[]): G14LegacyRetiremen
};
}
export function parseToolsImageOptions(args: string[]): G14ToolsImageOptions {
export function parseToolsImageOptions(args: string[]): LegacyRuntimeToolsImageOptions {
const [actionRaw] = args;
if (actionRaw !== "status" && actionRaw !== "build") {
throw new Error("tools-image usage: status|build --name ci-node-tools --tag <tag> [--dockerfile path] [--dry-run|--confirm]");
}
const name = optionValue(args, "--name") ?? "ci-node-tools";
if (name !== "ci-node-tools") throw new Error("tools-image currently supports --name ci-node-tools");
const tag = optionValue(args, "--tag") ?? G14_CI_TOOLS_BASE_TAG;
const tag = optionValue(args, "--tag") ?? LEGACY_RUNTIME_CI_TOOLS_BASE_TAG;
if (!/^[A-Za-z0-9_.-]+$/u.test(tag)) throw new Error("--tag may only contain letters, numbers, dot, underscore, and dash");
const dockerfile = optionValue(args, "--dockerfile") ?? "deploy/ci/hwlab-ci-node-tools.Dockerfile";
if (dockerfile.startsWith("/") || dockerfile.includes("..")) throw new Error("--dockerfile must be a repo-relative path without '..'");
@@ -205,7 +205,7 @@ export function parseToolsImageOptions(args: string[]): G14ToolsImageOptions {
};
}
export function parseUpstreamImageOptions(args: string[]): G14UpstreamImageOptions {
export function parseUpstreamImageOptions(args: string[]): LegacyRuntimeUpstreamImageOptions {
const [actionRaw] = args;
if (actionRaw !== "status" && actionRaw !== "ensure") {
throw new Error("upstream-image usage: status|ensure --name openfga --tag v1.17.0 [--dry-run|--confirm]");
@@ -227,7 +227,7 @@ export function parseUpstreamImageOptions(args: string[]): G14UpstreamImageOptio
};
}
export function parseGitMirrorOptions(args: string[]): G14GitMirrorOptions {
export function parseGitMirrorOptions(args: string[]): LegacyRuntimeGitMirrorOptions {
const [actionRaw] = args;
if (actionRaw !== "status" && actionRaw !== "apply" && actionRaw !== "sync" && actionRaw !== "flush") {
throw new Error("git-mirror usage: status|apply|sync|flush [--lane v02|v03] [--dry-run|--confirm]");
@@ -247,7 +247,7 @@ export function parseGitMirrorOptions(args: string[]): G14GitMirrorOptions {
};
}
export function parseObservabilityOptions(args: string[]): G14ObservabilityOptions {
export function parseObservabilityOptions(args: string[]): LegacyRuntimeObservabilityOptions {
const [actionRaw] = args;
if (
actionRaw !== "status" &&
@@ -301,7 +301,7 @@ export function parseObservabilityOptions(args: string[]): G14ObservabilityOptio
};
}
export function parseSecretOptions(args: string[]): G14SecretOptions {
export function parseSecretOptions(args: string[]): LegacyRuntimeSecretOptions {
const [actionRaw] = args;
if (actionRaw !== "status" && actionRaw !== "ensure" && actionRaw !== "delete") {
throw new Error("secret usage: status|ensure --lane v02|v03 --name hwlab-v0x-openfga|hwlab-v0x-master-server-admin-api-key [--dry-run|--confirm] | delete --lane v02 --name <obsolete-secret> [--dry-run|--confirm]");
@@ -321,7 +321,7 @@ export function parseSecretOptions(args: string[]): G14SecretOptions {
if (lane !== "v02") throw new Error("secret delete is currently limited to --lane v02 obsolete cleanup");
if (key !== undefined) throw new Error("secret delete does not accept --key; it deletes a whole obsolete Secret object");
if (!/^hwlab-v02-[a-z0-9-]+$/u.test(name)) throw new Error("secret delete requires a hwlab-v02-* Secret name");
if (name === V02_OPENFGA_SECRET || name === V02_MASTER_ADMIN_API_KEY_SECRET || name === "hwlab-v02-postgres") throw new Error(`secret delete refuses required v0.2 Secret ${name}`);
if (name === DEFAULT_RUNTIME_LANE_OPENFGA_SECRET || name === DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET || name === "hwlab-v02-postgres") throw new Error(`secret delete refuses required v0.2 Secret ${name}`);
return {
action: actionRaw,
lane,
@@ -333,14 +333,14 @@ export function parseSecretOptions(args: string[]): G14SecretOptions {
};
}
if (name === masterAdminSecret) {
if (key !== undefined && key !== V02_MASTER_ADMIN_API_KEY_SECRET_KEY) throw new Error(`secret ${masterAdminSecret} supports only key ${V02_MASTER_ADMIN_API_KEY_SECRET_KEY}`);
if (key !== undefined && key !== DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET_KEY) throw new Error(`secret ${masterAdminSecret} supports only key ${DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET_KEY}`);
return {
action: actionRaw,
lane,
confirm,
dryRun: actionRaw === "status" ? true : explicitDryRun || !confirm,
name,
key: key ?? V02_MASTER_ADMIN_API_KEY_SECRET_KEY,
key: key ?? DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET_KEY,
preset: "master-server-admin-api-key",
timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", 120, 600),
};
@@ -348,10 +348,10 @@ export function parseSecretOptions(args: string[]): G14SecretOptions {
if (name !== openFgaSecret) {
throw new Error(`secret status/ensure currently supports only --name ${openFgaSecret} or ${masterAdminSecret} for --lane ${lane}; use secret delete for obsolete v02 Secret objects`);
}
if (key !== undefined && key !== V02_OPENFGA_AUTHN_SECRET_KEY && key !== V02_OPENFGA_DATASTORE_URI_SECRET_KEY && key !== V02_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY) {
throw new Error(`secret ${openFgaSecret} supports keys ${V02_OPENFGA_AUTHN_SECRET_KEY}, ${V02_OPENFGA_DATASTORE_URI_SECRET_KEY}, and ${V02_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY}`);
if (key !== undefined && key !== DEFAULT_RUNTIME_LANE_OPENFGA_AUTHN_SECRET_KEY && key !== DEFAULT_RUNTIME_LANE_OPENFGA_DATASTORE_URI_SECRET_KEY && key !== DEFAULT_RUNTIME_LANE_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY) {
throw new Error(`secret ${openFgaSecret} supports keys ${DEFAULT_RUNTIME_LANE_OPENFGA_AUTHN_SECRET_KEY}, ${DEFAULT_RUNTIME_LANE_OPENFGA_DATASTORE_URI_SECRET_KEY}, and ${DEFAULT_RUNTIME_LANE_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY}`);
}
if (lane !== "v02" && key === V02_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY) throw new Error(`${openFgaSecret}/${V02_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY} is derived from ${postgresSecret}/POSTGRES_PASSWORD`);
if (lane !== "v02" && key === DEFAULT_RUNTIME_LANE_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY) throw new Error(`${openFgaSecret}/${DEFAULT_RUNTIME_LANE_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY} is derived from ${postgresSecret}/POSTGRES_PASSWORD`);
return {
action: actionRaw,
lane,
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. pr-monitor module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. pr-monitor module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:8387-9507 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:8387-9507 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -10,12 +10,12 @@ import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import type { CiPipelineMetrics, CiServiceMetric, CommandJsonResult, G14RecordRolloutOptions, OpenPullRequest, V02PrCommentInput } from "./types";
import type { CiPipelineMetrics, CiServiceMetric, CommandJsonResult, LegacyRuntimeRecordRolloutOptions, OpenPullRequest, DefaultRuntimeLanePrCommentInput } from "./types";
import { cliJson, commandData, commandJson, isCommandSuccess, nested, printEvent, record, shortSha, sleep, stringOrNull, textHash } from "./remote";
import { getV02Head } from "./source";
import { ARGO_NAMESPACE, BEIJING_OFFSET_MS, CI_NAMESPACE, DEV_APP, DEV_NAMESPACE, G14_BRIEF_INDEX_ISSUE, G14_PROVIDER, G14_WORKSPACE, HWLAB_REPO, V02_SOURCE_BRANCH } from "./types";
import { getDefaultRuntimeLaneHead } from "./source";
import { ARGO_NAMESPACE, BEIJING_OFFSET_MS, CI_NAMESPACE, DEV_APP, DEV_NAMESPACE, LEGACY_RUNTIME_BRIEF_INDEX_ISSUE, LEGACY_RUNTIME_PROVIDER, LEGACY_RUNTIME_WORKSPACE, HWLAB_REPO, DEFAULT_RUNTIME_LANE_SOURCE_BRANCH } from "./types";
export function listOpenG14PullRequests(): CommandJsonResult {
export function listOpenLegacyRuntimePullRequests(): CommandJsonResult {
return cliJson(["gh", "pr", "list", "--repo", HWLAB_REPO, "--state", "open", "--limit", "30", "--json", "number,title,state,url,head,base,draft,headRefName,baseRefName"], 60_000);
}
@@ -27,8 +27,8 @@ export function mergePullRequest(number: number, dryRun: boolean): CommandJsonRe
return cliJson(["gh", "pr", "merge", String(number), "--repo", HWLAB_REPO, "--merge", "--delete-branch", ...(dryRun ? ["--dry-run"] : [])], 100_000);
}
export function getG14Head(): string | null {
const result = cliJson(["ssh", `${G14_PROVIDER}:${G14_WORKSPACE}`, "sh", "--", "git fetch origin G14 --prune >/dev/null 2>&1; git rev-parse origin/G14"], 120_000);
export function getLegacyRuntimeHead(): string | null {
const result = cliJson(["ssh", `${LEGACY_RUNTIME_PROVIDER}:${LEGACY_RUNTIME_WORKSPACE}`, "sh", "--", "git fetch origin G14 --prune >/dev/null 2>&1; git rev-parse origin/G14"], 120_000);
if (!isCommandSuccess(result)) return null;
const output = String(nested(result.parsed, ["data", "stdout"]) ?? result.stdout).trim();
const match = /[0-9a-f]{40}/iu.exec(output);
@@ -36,19 +36,19 @@ export function getG14Head(): string | null {
}
export function refreshArgoDev(): void {
cliJson(["ssh", `${G14_PROVIDER}:k3s`, "kubectl", "annotate", "application", "-n", ARGO_NAMESPACE, DEV_APP, "argocd.argoproj.io/refresh=hard", "--overwrite"], 60_000);
cliJson(["ssh", `${LEGACY_RUNTIME_PROVIDER}:k3s`, "kubectl", "annotate", "application", "-n", ARGO_NAMESPACE, DEV_APP, "argocd.argoproj.io/refresh=hard", "--overwrite"], 60_000);
}
export function getPipelineStatus(sourceCommit: string): CommandJsonResult {
return cliJson(["ssh", `${G14_PROVIDER}:k3s`, "kubectl", "get", "pipelinerun", "-n", CI_NAMESPACE, `hwlab-g14-ci-poll-${shortSha(sourceCommit)}`, "-o", "jsonpath={.status.conditions[0].status}{\"\\n\"}{.status.conditions[0].reason}{\"\\n\"}{.status.conditions[0].message}{\"\\n\"}"], 60_000);
return cliJson(["ssh", `${LEGACY_RUNTIME_PROVIDER}:k3s`, "kubectl", "get", "pipelinerun", "-n", CI_NAMESPACE, `hwlab-g14-ci-poll-${shortSha(sourceCommit)}`, "-o", "jsonpath={.status.conditions[0].status}{\"\\n\"}{.status.conditions[0].reason}{\"\\n\"}{.status.conditions[0].message}{\"\\n\"}"], 60_000);
}
export function getArgoStatus(): CommandJsonResult {
return cliJson(["ssh", `${G14_PROVIDER}:k3s`, "kubectl", "get", "application", "-n", ARGO_NAMESPACE, DEV_APP, "-o", "jsonpath={.status.sync.revision}{\"\\n\"}{.status.sync.status}{\"\\n\"}{.status.health.status}{\"\\n\"}{.status.operationState.phase}{\"\\n\"}{.status.operationState.syncResult.revision}{\"\\n\"}"], 60_000);
return cliJson(["ssh", `${LEGACY_RUNTIME_PROVIDER}:k3s`, "kubectl", "get", "application", "-n", ARGO_NAMESPACE, DEV_APP, "-o", "jsonpath={.status.sync.revision}{\"\\n\"}{.status.sync.status}{\"\\n\"}{.status.health.status}{\"\\n\"}{.status.operationState.phase}{\"\\n\"}{.status.operationState.syncResult.revision}{\"\\n\"}"], 60_000);
}
export function getDevWorkloads(): CommandJsonResult {
return cliJson(["ssh", `${G14_PROVIDER}:k3s`, "kubectl", "get", "deploy,statefulset,pod", "-n", DEV_NAMESPACE, "-o", "wide"], 60_000);
return cliJson(["ssh", `${LEGACY_RUNTIME_PROVIDER}:k3s`, "kubectl", "get", "deploy,statefulset,pod", "-n", DEV_NAMESPACE, "-o", "wide"], 60_000);
}
export function getLiveHealth(): CommandJsonResult {
@@ -140,7 +140,7 @@ export function pipelineTaskRunResultsJsonPath(): string {
export function getPipelineTaskRunResults(pipelineRun: string): CommandJsonResult {
return cliJson([
"ssh",
`${G14_PROVIDER}:k3s`,
`${LEGACY_RUNTIME_PROVIDER}:k3s`,
"kubectl",
"get",
"taskrun",
@@ -462,7 +462,7 @@ export function insertBriefIndexRow(indexBody: string, date: string, brief: { nu
}
export function ensureDailyBriefIssue(date: string, dryRun: boolean): Record<string, unknown> {
const indexRead = readIssue(G14_BRIEF_INDEX_ISSUE);
const indexRead = readIssue(LEGACY_RUNTIME_BRIEF_INDEX_ISSUE);
if (!isCommandSuccess(indexRead)) return { ok: false, phase: "read-index", indexRead };
const indexIssue = issueFromRead(indexRead);
const indexBody = String(indexIssue.body ?? "");
@@ -483,14 +483,14 @@ export function ensureDailyBriefIssue(date: string, dryRun: boolean): Record<str
}
if (brief.number > 0 && parseBriefIssueFromIndex(indexBody, date) === null) {
const nextBody = insertBriefIndexRow(indexBody, date, brief);
const bodyPath = writeStateFile(`issue-${G14_BRIEF_INDEX_ISSUE}-brief-index-${date}.md`, `${nextBody.trimEnd()}\n`);
const bodyPath = writeStateFile(`issue-${LEGACY_RUNTIME_BRIEF_INDEX_ISSUE}-brief-index-${date}.md`, `${nextBody.trimEnd()}\n`);
if (dryRun) {
actions.push({ action: "would-update-brief-index", issue: G14_BRIEF_INDEX_ISSUE, bodyPath });
actions.push({ action: "would-update-brief-index", issue: LEGACY_RUNTIME_BRIEF_INDEX_ISSUE, bodyPath });
} else {
const bodySha = String(indexIssue.bodySha ?? "");
const update = cliJson(["gh", "issue", "update", String(G14_BRIEF_INDEX_ISSUE), "--repo", HWLAB_REPO, "--mode", "replace", "--body-file", bodyPath, ...(bodySha.length > 0 ? ["--expect-body-sha", bodySha] : [])], 100_000);
const update = cliJson(["gh", "issue", "update", String(LEGACY_RUNTIME_BRIEF_INDEX_ISSUE), "--repo", HWLAB_REPO, "--mode", "replace", "--body-file", bodyPath, ...(bodySha.length > 0 ? ["--expect-body-sha", bodySha] : [])], 100_000);
if (!isCommandSuccess(update)) return { ok: false, phase: "update-brief-index", update, bodyPath };
actions.push({ action: "updated-brief-index", issue: G14_BRIEF_INDEX_ISSUE, bodyPath });
actions.push({ action: "updated-brief-index", issue: LEGACY_RUNTIME_BRIEF_INDEX_ISSUE, bodyPath });
}
}
return { ok: true, date, brief, actions };
@@ -531,11 +531,11 @@ export function sourceCommitFromMergeResult(merge: CommandJsonResult, prNumber:
return mergeCommitFromPr(commandData(prRead));
}
export async function waitForV02Head(expectedCommit: string | null, timeoutSeconds: number): Promise<Record<string, unknown>> {
export async function waitForDefaultRuntimeLaneHead(expectedCommit: string | null, timeoutSeconds: number): Promise<Record<string, unknown>> {
const started = Date.now();
let head: string | null = null;
while (Date.now() - started < timeoutSeconds * 1000) {
head = getV02Head();
head = getDefaultRuntimeLaneHead();
if (expectedCommit === null) {
if (head !== null) return { ok: true, sourceCommit: head, expectedCommit, matched: true, waitedSeconds: Math.round((Date.now() - started) / 1000) };
} else if (head === expectedCommit) {
@@ -606,7 +606,7 @@ export function rolloutRecordBody(input: {
].join("\n");
}
export function appendRolloutBrief(options: G14RecordRolloutOptions, rollout: Record<string, unknown> = {}): Record<string, unknown> {
export function appendRolloutBrief(options: LegacyRuntimeRecordRolloutOptions, rollout: Record<string, unknown> = {}): Record<string, unknown> {
const now = beijingParts();
const ensured = ensureDailyBriefIssue(now.date, options.dryRun);
if (record(ensured).ok !== true) return ensured;
@@ -656,12 +656,12 @@ export function appendRolloutBrief(options: G14RecordRolloutOptions, rollout: Re
return { ok: true, date: now.date, brief, sourceCommit, pipelineRun, gitopsRevision, ciMetrics, bodyPath, update: commandData(update), ensured };
}
export function v02PrCommentStatePath(): string {
export function defaultRuntimeLanePrCommentStatePath(): string {
return rootPath(".state", "hwlab-g14", "v02-pr-comment-signatures.json");
}
export function readV02PrCommentState(): Record<string, string> {
const path = v02PrCommentStatePath();
export function readDefaultRuntimeLanePrCommentState(): Record<string, string> {
const path = defaultRuntimeLanePrCommentStatePath();
if (!existsSync(path)) return {};
try {
const parsed = JSON.parse(readFileSync(path, "utf8")) as unknown;
@@ -675,8 +675,8 @@ export function readV02PrCommentState(): Record<string, string> {
}
}
export function writeV02PrCommentState(state: Record<string, string>): string {
const path = v02PrCommentStatePath();
export function writeDefaultRuntimeLanePrCommentState(state: Record<string, string>): string {
const path = defaultRuntimeLanePrCommentStatePath();
mkdirSync(dirname(path), { recursive: true });
writeFileSync(path, `${JSON.stringify(state, null, 2)}\n`, "utf8");
return path;
@@ -699,7 +699,7 @@ export function preflightSummary(preflight: CommandJsonResult): Record<string, u
};
}
export function v02PrConflictState(summary: Record<string, unknown>): string {
export function defaultRuntimeLanePrConflictState(summary: Record<string, unknown>): string {
const mergeable = String(summary.mergeable ?? "").toUpperCase();
const mergeStateStatus = String(summary.mergeStateStatus ?? "").toUpperCase();
const blockers = Array.isArray(summary.blockers) ? summary.blockers.map(String).join("\n") : "";
@@ -707,7 +707,7 @@ export function v02PrConflictState(summary: Record<string, unknown>): string {
return "clear-or-unknown";
}
export function summarizeV02CdStatus(status: Record<string, unknown>): Record<string, unknown> {
export function summarizeDefaultRuntimeLaneCdStatus(status: Record<string, unknown>): Record<string, unknown> {
const pipelineRun = record(status.pipelineRun);
const targetValidation = record(status.targetValidation);
const gitMirror = record(status.gitMirror);
@@ -737,12 +737,12 @@ export function summarizeV02CdStatus(status: Record<string, unknown>): Record<st
};
}
export function v02CdPassed(status: Record<string, unknown>): boolean {
export function defaultRuntimeLaneCdPassed(status: Record<string, unknown>): boolean {
const state = String(nested(status, ["targetValidation", "state"]) ?? "");
return state === "passed" || state === "superseded";
}
export function v02ControlPlaneCloseout(status: Record<string, unknown>): Record<string, unknown> {
export function defaultRuntimeLaneControlPlaneCloseout(status: Record<string, unknown>): Record<string, unknown> {
const closeout = record(status.closeout);
const sourceCommit = stringOrNull(status.sourceCommit);
const pipelineRun = stringOrNull(nested(status, ["statusTarget", "pipelineRun"])) ?? stringOrNull(nested(status, ["pipelineRun", "pipelineRun"]));
@@ -769,16 +769,16 @@ export function v02ControlPlaneCloseout(status: Record<string, unknown>): Record
};
}
export function v02PipelineSucceeded(status: Record<string, unknown>): boolean {
export function defaultRuntimeLanePipelineSucceeded(status: Record<string, unknown>): boolean {
return String(nested(status, ["pipelineRun", "status"]) ?? "") === "True";
}
export function v02CdFailed(status: Record<string, unknown>): boolean {
export function defaultRuntimeLaneCdFailed(status: Record<string, unknown>): boolean {
const pipelineStatus = String(nested(status, ["pipelineRun", "status"]) ?? "");
return pipelineStatus === "False";
}
export function activeV02PipelineRuns(status: Record<string, unknown>): Record<string, unknown>[] {
export function activeDefaultRuntimeLanePipelineRuns(status: Record<string, unknown>): Record<string, unknown>[] {
const items = Array.isArray(status.activePipelineRuns)
? status.activePipelineRuns
: Array.isArray(record(status.activePipelineRuns).items)
@@ -787,7 +787,7 @@ export function activeV02PipelineRuns(status: Record<string, unknown>): Record<s
return items.map((item) => record(item)).filter((item) => String(item.status ?? "") === "Unknown");
}
export function v02StatusHistoryPolicy(status: Record<string, unknown>): Record<string, unknown> {
export function defaultRuntimeLaneStatusHistoryPolicy(status: Record<string, unknown>): Record<string, unknown> {
const history = record(status.history);
return {
included: history.included === true,
@@ -798,7 +798,7 @@ export function v02StatusHistoryPolicy(status: Record<string, unknown>): Record<
};
}
export function v02PrCommentSignature(input: V02PrCommentInput): string {
export function defaultRuntimeLanePrCommentSignature(input: DefaultRuntimeLanePrCommentInput): string {
const summary = input.preflight ?? {};
const cd = input.cd ?? {};
const flush = input.flush ?? {};
@@ -808,7 +808,7 @@ export function v02PrCommentSignature(input: V02PrCommentInput): string {
phase: input.phase,
conclusion: summary.conclusion ?? null,
readyForCommanderMerge: summary.readyForCommanderMerge ?? null,
conflict: v02PrConflictState(summary),
conflict: defaultRuntimeLanePrConflictState(summary),
blockers: summary.blockers ?? [],
pending: summary.pending ?? [],
sourceCommit: input.sourceCommit ?? null,
@@ -833,7 +833,7 @@ export function scalarValue(value: unknown): string {
return String(value);
}
export function v02PrAutomationCommentBody(input: V02PrCommentInput): string {
export function defaultRuntimeLanePrAutomationCommentBody(input: DefaultRuntimeLanePrCommentInput): string {
const preflight = input.preflight ?? {};
const cd = input.cd ?? {};
const flush = input.flush ?? {};
@@ -848,12 +848,12 @@ export function v02PrAutomationCommentBody(input: V02PrCommentInput): string {
"",
"- PR:",
` - [#${input.pr.number} ${title}](${url})`,
` - base: \`${input.pr.baseRefName ?? V02_SOURCE_BRANCH}\`; head: \`${input.pr.headRefName ?? "unknown"}\``,
` - base: \`${input.pr.baseRefName ?? DEFAULT_RUNTIME_LANE_SOURCE_BRANCH}\`; head: \`${input.pr.headRefName ?? "unknown"}\``,
"- 自动化状态:",
` - state: \`${input.state}\`; phase: \`${input.phase}\``,
` - startedAt: \`${input.startedAt}\`; observedAt: \`${input.observedAt}\`; elapsed: ${elapsed}`,
"- CI / mergeability:",
` - conclusion: \`${scalarValue(preflight.conclusion)}\`; readyForCommanderMerge: \`${String(preflight.readyForCommanderMerge ?? "n/a")}\`; conflict: \`${v02PrConflictState(preflight)}\``,
` - conclusion: \`${scalarValue(preflight.conclusion)}\`; readyForCommanderMerge: \`${String(preflight.readyForCommanderMerge ?? "n/a")}\`; conflict: \`${defaultRuntimeLanePrConflictState(preflight)}\``,
` - blockers: ${listValue(preflight.blockers)}`,
` - pending: ${listValue(preflight.pending)}`,
"- CD / runtime:",
@@ -868,22 +868,22 @@ export function v02PrAutomationCommentBody(input: V02PrCommentInput): string {
].join("\n");
}
export function commentV02PullRequest(input: V02PrCommentInput): Record<string, unknown> {
const body = v02PrAutomationCommentBody(input);
const signature = v02PrCommentSignature(input);
export function commentDefaultRuntimeLanePullRequest(input: DefaultRuntimeLanePrCommentInput): Record<string, unknown> {
const body = defaultRuntimeLanePrAutomationCommentBody(input);
const signature = defaultRuntimeLanePrCommentSignature(input);
const cacheKey = `pr-${input.pr.number}`;
if (input.dryRun === true) {
return { ok: true, dryRun: true, skipped: true, signature, wouldComment: { bodyPreview: body.slice(0, 1200), bodyChars: body.length } };
}
const state = readV02PrCommentState();
const state = readDefaultRuntimeLanePrCommentState();
if (state[cacheKey] === signature) {
return { ok: true, skipped: true, reason: "same-pr-comment-signature", signature, stateFile: v02PrCommentStatePath() };
return { ok: true, skipped: true, reason: "same-pr-comment-signature", signature, stateFile: defaultRuntimeLanePrCommentStatePath() };
}
const bodyPath = writeStateFile(`v02-pr-${input.pr.number}-${input.state}-${signature}.md`, `${body}\n`);
const create = cliJson(["gh", "pr", "comment", "create", String(input.pr.number), "--repo", HWLAB_REPO, "--body-file", bodyPath], 100_000);
if (!isCommandSuccess(create)) return { ok: false, phase: "pr-comment", signature, bodyPath, create };
state[cacheKey] = signature;
const stateFile = writeV02PrCommentState(state);
const stateFile = writeDefaultRuntimeLanePrCommentState(state);
return { ok: true, signature, bodyPath, stateFile, create: commandData(create) };
}
@@ -954,7 +954,7 @@ export function runtimeLaneCdFailed(status: Record<string, unknown>): boolean {
return String(nested(status, ["pipelineRun", "status"]) ?? "") === "False";
}
export function runtimeLanePrCommentSignature(spec: HwlabRuntimeLaneSpec, input: V02PrCommentInput): string {
export function runtimeLanePrCommentSignature(spec: HwlabRuntimeLaneSpec, input: DefaultRuntimeLanePrCommentInput): string {
const summary = input.preflight ?? {};
const cd = input.cd ?? {};
const flush = input.flush ?? {};
@@ -965,7 +965,7 @@ export function runtimeLanePrCommentSignature(spec: HwlabRuntimeLaneSpec, input:
phase: input.phase,
conclusion: summary.conclusion ?? null,
readyForCommanderMerge: summary.readyForCommanderMerge ?? null,
conflict: v02PrConflictState(summary),
conflict: defaultRuntimeLanePrConflictState(summary),
blockers: summary.blockers ?? [],
pending: summary.pending ?? [],
sourceCommit: input.sourceCommit ?? null,
@@ -980,7 +980,7 @@ export function runtimeLanePrCommentSignature(spec: HwlabRuntimeLaneSpec, input:
}));
}
export function runtimeLanePrAutomationCommentBody(spec: HwlabRuntimeLaneSpec, input: V02PrCommentInput): string {
export function runtimeLanePrAutomationCommentBody(spec: HwlabRuntimeLaneSpec, input: DefaultRuntimeLanePrCommentInput): string {
const preflight = input.preflight ?? {};
const cd = input.cd ?? {};
const flush = input.flush ?? {};
@@ -1000,7 +1000,7 @@ export function runtimeLanePrAutomationCommentBody(spec: HwlabRuntimeLaneSpec, i
` - lane: \`${spec.lane}\`; state: \`${input.state}\`; phase: \`${input.phase}\``,
` - startedAt: \`${input.startedAt}\`; observedAt: \`${input.observedAt}\`; elapsed: ${elapsed}`,
"- CI / mergeability:",
` - conclusion: \`${scalarValue(preflight.conclusion)}\`; readyForCommanderMerge: \`${String(preflight.readyForCommanderMerge ?? "n/a")}\`; conflict: \`${v02PrConflictState(preflight)}\``,
` - conclusion: \`${scalarValue(preflight.conclusion)}\`; readyForCommanderMerge: \`${String(preflight.readyForCommanderMerge ?? "n/a")}\`; conflict: \`${defaultRuntimeLanePrConflictState(preflight)}\``,
` - blockers: ${listValue(preflight.blockers)}`,
` - pending: ${listValue(preflight.pending)}`,
"- CD / runtime:",
@@ -1015,7 +1015,7 @@ export function runtimeLanePrAutomationCommentBody(spec: HwlabRuntimeLaneSpec, i
].join("\n");
}
export function commentRuntimeLanePullRequest(spec: HwlabRuntimeLaneSpec, input: V02PrCommentInput): Record<string, unknown> {
export function commentRuntimeLanePullRequest(spec: HwlabRuntimeLaneSpec, input: DefaultRuntimeLanePrCommentInput): Record<string, unknown> {
const body = runtimeLanePrAutomationCommentBody(spec, input);
const signature = runtimeLanePrCommentSignature(spec, input);
const cacheKey = `pr-${input.pr.number}`;
@@ -1073,7 +1073,7 @@ export function runtimeLaneAutomationIssueBody(input: {
` - startedAt: \`${input.startedAt}\`; observedAt: \`${input.observedAt}\``,
` - dedupeKey: \`${dedupeKey}\``,
"- CI / mergeability:",
` - conclusion: \`${scalarValue(preflight.conclusion)}\`; readyForCommanderMerge: \`${String(preflight.readyForCommanderMerge ?? "n/a")}\`; conflict: \`${v02PrConflictState(preflight)}\``,
` - conclusion: \`${scalarValue(preflight.conclusion)}\`; readyForCommanderMerge: \`${String(preflight.readyForCommanderMerge ?? "n/a")}\`; conflict: \`${defaultRuntimeLanePrConflictState(preflight)}\``,
` - blockers: ${listValue(preflight.blockers)}`,
` - pending: ${listValue(preflight.pending)}`,
"- CD / runtime:",
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. remote module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. remote module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:639-1204 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:639-1204 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -10,9 +10,9 @@ import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import type { CommandJsonResult, G14MonitorLane, OpenPullRequest, RemoteAsyncCommandSpec } from "./types";
import type { CommandJsonResult, LegacyRuntimeMonitorLane, OpenPullRequest, RemoteAsyncCommandSpec } from "./types";
import { tailText } from "./cleanup";
import { G14_PROVIDER, G14_SOURCE_BRANCH, G14_WORKSPACE, V02_MASTER_ADMIN_API_KEY_LOCAL_ENV } from "./types";
import { LEGACY_RUNTIME_PROVIDER, LEGACY_RUNTIME_SOURCE_BRANCH, LEGACY_RUNTIME_WORKSPACE, DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_LOCAL_ENV } from "./types";
export function positiveIntegerOption(args: string[], name: string, defaultValue: number, maxValue: number): number {
const index = args.indexOf(name);
@@ -172,7 +172,7 @@ export function runtimeLaneMasterAdminApiKeySecretName(spec: HwlabRuntimeLaneSpe
}
export function runtimeLaneMasterAdminApiKeyLocalEnv(spec: HwlabRuntimeLaneSpec): string {
if (spec.lane === "v02") return V02_MASTER_ADMIN_API_KEY_LOCAL_ENV;
if (spec.lane === "v02") return DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_LOCAL_ENV;
return `/root/.config/hwlab-${spec.lane}/master-server-admin-api-key.env`;
}
@@ -306,11 +306,11 @@ export function isCommandSuccess(result: CommandJsonResult): boolean {
return dataOk !== false;
}
export function monitorBaseBranch(lane: G14MonitorLane): string {
return lane === "g14" ? G14_SOURCE_BRANCH : hwlabRuntimeLaneSpec(lane).sourceBranch;
export function monitorBaseBranch(lane: LegacyRuntimeMonitorLane): string {
return lane === "g14" ? LEGACY_RUNTIME_SOURCE_BRANCH : hwlabRuntimeLaneSpec(lane).sourceBranch;
}
export function extractPullRequests(result: CommandJsonResult, baseBranch = G14_SOURCE_BRANCH): OpenPullRequest[] {
export function extractPullRequests(result: CommandJsonResult, baseBranch = LEGACY_RUNTIME_SOURCE_BRANCH): OpenPullRequest[] {
const prs = nested(result.parsed, ["data", "pullRequests"]);
if (!Array.isArray(prs)) return [];
return prs
@@ -341,7 +341,7 @@ export function printProgressEvent(event: string, data: Record<string, unknown>
process.stderr.write(`${JSON.stringify({ event, at: new Date().toISOString(), ...data })}\n`);
}
export function printV02PrMonitorProgress(data: Record<string, unknown> = {}): void {
export function printDefaultRuntimeLanePrMonitorProgress(data: Record<string, unknown> = {}): void {
printProgressEvent("hwlab.v02.pr-monitor.progress", data);
}
@@ -366,19 +366,19 @@ export function shortSha(sha: string): string {
}
export function precheckWorkspace(): CommandJsonResult {
return cliJson(["ssh", `${G14_PROVIDER}:${G14_WORKSPACE}`, "sh", "--", "pwd; git fetch origin G14 --prune; git status --short --branch; git remote -v | sed -n '1,4p'"], 120_000);
return cliJson(["ssh", `${LEGACY_RUNTIME_PROVIDER}:${LEGACY_RUNTIME_WORKSPACE}`, "sh", "--", "pwd; git fetch origin G14 --prune; git status --short --branch; git remote -v | sed -n '1,4p'"], 120_000);
}
export function g14HostScript(script: string, timeoutMs = 120_000): CommandJsonResult {
return cliJson(["ssh", G14_PROVIDER, "sh", "--", script], timeoutMs);
export function legacyRuntimeHostScript(script: string, timeoutMs = 120_000): CommandJsonResult {
return cliJson(["ssh", LEGACY_RUNTIME_PROVIDER, "sh", "--", script], timeoutMs);
}
export function g14K3s(args: string[], timeoutMs = 60_000): CommandJsonResult {
return cliJson(["ssh", `${G14_PROVIDER}:k3s`, ...args], timeoutMs);
export function legacyRuntimeK3s(args: string[], timeoutMs = 60_000): CommandJsonResult {
return cliJson(["ssh", `${LEGACY_RUNTIME_PROVIDER}:k3s`, ...args], timeoutMs);
}
export function g14K3sInlineScriptWithInput(script: string, input: string, timeoutMs = 60_000): CommandJsonResult {
return commandJsonWithInput(["bun", "scripts/cli.ts", "ssh", `${G14_PROVIDER}:k3s`, "sh", "--", script], input, timeoutMs);
export function legacyRuntimeK3sInlineScriptWithInput(script: string, input: string, timeoutMs = 60_000): CommandJsonResult {
return commandJsonWithInput(["bun", "scripts/cli.ts", "ssh", `${LEGACY_RUNTIME_PROVIDER}:k3s`, "sh", "--", script], input, timeoutMs);
}
export function remoteAsyncStatusDir(label: string): string {
@@ -511,9 +511,9 @@ export function parseRemoteAsyncPayload(result: CommandJsonResult): Record<strin
}
}
export function runG14K3sRemoteAsync(spec: RemoteAsyncCommandSpec): CommandJsonResult {
export function runLegacyRuntimeK3sRemoteAsync(spec: RemoteAsyncCommandSpec): CommandJsonResult {
const startedAtMs = Date.now();
const start = g14K3s(["sh", "--", remoteAsyncStartScript(spec)], 55_000);
const start = legacyRuntimeK3s(["sh", "--", remoteAsyncStartScript(spec)], 55_000);
const startPayload = parseRemoteAsyncPayload(start);
if (!isCommandSuccess(start) || startPayload.ok !== true) return start;
const deadlineMs = startedAtMs + Math.max(1, spec.timeoutSeconds) * 1000;
@@ -523,7 +523,7 @@ export function runG14K3sRemoteAsync(spec: RemoteAsyncCommandSpec): CommandJsonR
attempts += 1;
const wait = runCommand(["sleep", attempts <= 1 ? "1" : "2"], repoRoot, { timeoutMs: 3_000 });
if (wait.exitCode !== 0 && wait.timedOut) break;
const status = g14K3s(["sh", "--", remoteAsyncStatusScript(spec.label, spec.token)], 55_000);
const status = legacyRuntimeK3s(["sh", "--", remoteAsyncStatusScript(spec.label, spec.token)], 55_000);
lastStatus = status;
const payload = parseRemoteAsyncPayload(status);
const state = typeof payload.state === "string" ? payload.state : null;
@@ -550,7 +550,7 @@ export function runG14K3sRemoteAsync(spec: RemoteAsyncCommandSpec): CommandJsonR
};
}
}
const killed = g14K3s(["sh", "--", remoteAsyncKillScript(spec.label, spec.token)], 55_000);
const killed = legacyRuntimeK3s(["sh", "--", remoteAsyncKillScript(spec.label, spec.token)], 55_000);
const payload = parseRemoteAsyncPayload(killed);
return {
ok: false,
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. render module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. render module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:3346-3736 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:3346-3736 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -11,11 +11,11 @@ import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import type { CommandJsonResult } from "./types";
import { g14HostScript } from "./images";
import { compactCommandResult, g14K3s, isCommandSuccess, record, runG14K3sRemoteAsync, shellQuote, shortSha, textHash } from "./remote";
import { legacyRuntimeHostScript } from "./images";
import { compactCommandResult, legacyRuntimeK3s, isCommandSuccess, record, runLegacyRuntimeK3sRemoteAsync, shellQuote, shortSha, textHash } from "./remote";
import { runtimeLaneCicdRepoEnsureScript } from "./source";
import { CI_NAMESPACE, G14_PROVIDER, V02_CONTROL_PLANE_REFRESH_LOCK_STALE_MS, V02_CONTROL_PLANE_REFRESH_TTL_MS, V02_LANE_SPEC, V02_POLLER, V02_RECONCILER } from "./types";
import { timestampMs } from "./v02-status";
import { CI_NAMESPACE, LEGACY_RUNTIME_PROVIDER, DEFAULT_RUNTIME_LANE_CONTROL_PLANE_REFRESH_LOCK_STALE_MS, DEFAULT_RUNTIME_LANE_CONTROL_PLANE_REFRESH_TTL_MS, DEFAULT_RUNTIME_LANE_LANE_SPEC, DEFAULT_RUNTIME_LANE_POLLER, DEFAULT_RUNTIME_LANE_RECONCILER } from "./types";
import { timestampMs } from "./runtime-status";
export function runtimeLaneControlPlaneRenderScript(spec: HwlabRuntimeLaneSpec, sourceCommit: string, token = runtimeLaneRenderToken()): string {
const renderDir = runtimeLaneRenderDir(spec, sourceCommit, token);
@@ -63,29 +63,29 @@ export function runtimeLaneControlPlaneRenderScript(spec: HwlabRuntimeLaneSpec,
].join("\n");
}
export function v02ControlPlaneRenderScript(sourceCommit: string, token = v02RenderToken()): string {
return runtimeLaneControlPlaneRenderScript(V02_LANE_SPEC, sourceCommit, token);
export function defaultRuntimeLaneControlPlaneRenderScript(sourceCommit: string, token = defaultRuntimeLaneRenderToken()): string {
return runtimeLaneControlPlaneRenderScript(DEFAULT_RUNTIME_LANE_LANE_SPEC, sourceCommit, token);
}
export interface V02RenderTempResult {
export interface DefaultRuntimeLaneRenderTempResult {
result: CommandJsonResult;
renderDir: string;
worktreeDir: string;
token: string;
}
export function runRuntimeLaneRenderToTemp(spec: HwlabRuntimeLaneSpec, sourceCommit: string): V02RenderTempResult {
export function runRuntimeLaneRenderToTemp(spec: HwlabRuntimeLaneSpec, sourceCommit: string): DefaultRuntimeLaneRenderTempResult {
const token = runtimeLaneRenderToken();
return {
result: g14HostScript(runtimeLaneControlPlaneRenderScript(spec, sourceCommit, token), 180_000),
result: legacyRuntimeHostScript(runtimeLaneControlPlaneRenderScript(spec, sourceCommit, token), 180_000),
renderDir: runtimeLaneRenderDir(spec, sourceCommit, token),
worktreeDir: runtimeLaneRenderWorktreeDir(spec, sourceCommit, token),
token,
};
}
export function runV02RenderToTemp(sourceCommit: string): V02RenderTempResult {
return runRuntimeLaneRenderToTemp(V02_LANE_SPEC, sourceCommit);
export function runDefaultRuntimeLaneRenderToTemp(sourceCommit: string): DefaultRuntimeLaneRenderTempResult {
return runRuntimeLaneRenderToTemp(DEFAULT_RUNTIME_LANE_LANE_SPEC, sourceCommit);
}
export function runtimeLaneRenderToken(): string {
@@ -93,7 +93,7 @@ export function runtimeLaneRenderToken(): string {
return `${process.pid}-${Date.now().toString(36)}-${random}`.replace(/[^a-zA-Z0-9_.-]/gu, "-");
}
export function v02RenderToken(): string {
export function defaultRuntimeLaneRenderToken(): string {
return runtimeLaneRenderToken();
}
@@ -101,20 +101,20 @@ export function runtimeLaneRenderDir(spec: HwlabRuntimeLaneSpec, sourceCommit: s
return `/tmp/hwlab-${spec.lane}-control-plane-${shortSha(sourceCommit)}${token ? `-${token}` : ""}`;
}
export function v02RenderDir(sourceCommit: string, token?: string): string {
return runtimeLaneRenderDir(V02_LANE_SPEC, sourceCommit, token);
export function defaultRuntimeLaneRenderDir(sourceCommit: string, token?: string): string {
return runtimeLaneRenderDir(DEFAULT_RUNTIME_LANE_LANE_SPEC, sourceCommit, token);
}
export function runtimeLaneRenderWorktreeDir(spec: HwlabRuntimeLaneSpec, sourceCommit: string, token?: string): string {
return `/tmp/hwlab-${spec.lane}-control-plane-source-${shortSha(sourceCommit)}${token ? `-${token}` : ""}`;
}
export function v02RenderWorktreeDir(sourceCommit: string, token?: string): string {
return runtimeLaneRenderWorktreeDir(V02_LANE_SPEC, sourceCommit, token);
export function defaultRuntimeLaneRenderWorktreeDir(sourceCommit: string, token?: string): string {
return runtimeLaneRenderWorktreeDir(DEFAULT_RUNTIME_LANE_LANE_SPEC, sourceCommit, token);
}
export function cleanupRuntimeLaneRenderDir(spec: HwlabRuntimeLaneSpec, renderDir: string): CommandJsonResult {
return g14HostScript([
return legacyRuntimeHostScript([
"set -eu",
`render_dir=${shellQuote(renderDir)}`,
"case \"$render_dir\" in",
@@ -124,8 +124,8 @@ export function cleanupRuntimeLaneRenderDir(spec: HwlabRuntimeLaneSpec, renderDi
].join("\n"), 60_000);
}
export function cleanupV02RenderDir(renderDir: string): CommandJsonResult {
return cleanupRuntimeLaneRenderDir(V02_LANE_SPEC, renderDir);
export function cleanupDefaultRuntimeLaneRenderDir(renderDir: string): CommandJsonResult {
return cleanupRuntimeLaneRenderDir(DEFAULT_RUNTIME_LANE_LANE_SPEC, renderDir);
}
export function legacyRuntimeLaneArgoApplications(spec: HwlabRuntimeLaneSpec): string[] {
@@ -150,7 +150,7 @@ export function applyRuntimeLaneControlPlaneFiles(spec: HwlabRuntimeLaneSpec, re
...(dryRun ? ["--dry-run=server"] : []),
...resourceFiles.flatMap((file) => ["-f", file]),
];
const command = ["bun", "scripts/cli.ts", "ssh", `${G14_PROVIDER}:k3s`, ...serverSideArgs];
const command = ["bun", "scripts/cli.ts", "ssh", `${LEGACY_RUNTIME_PROVIDER}:k3s`, ...serverSideArgs];
const shellCommand = dryRun && spec.lane !== "v02"
? [
"set -eu",
@@ -187,9 +187,9 @@ export function applyRuntimeLaneControlPlaneFiles(spec: HwlabRuntimeLaneSpec, re
? ["set -eu", shellCommand.replace(/^exec /, ""), ...cleanupCommands].join("\n")
: shellCommand;
const visibleCommand = cleanupCommands.length > 0
? ["bun", "scripts/cli.ts", "ssh", `${G14_PROVIDER}:k3s`, "sh", "--", script]
? ["bun", "scripts/cli.ts", "ssh", `${LEGACY_RUNTIME_PROVIDER}:k3s`, "sh", "--", script]
: command;
return runG14K3sRemoteAsync({
return runLegacyRuntimeK3sRemoteAsync({
script,
timeoutSeconds,
label: `${spec.lane}-control-plane-apply`,
@@ -198,11 +198,11 @@ export function applyRuntimeLaneControlPlaneFiles(spec: HwlabRuntimeLaneSpec, re
});
}
export function applyV02ControlPlaneFiles(renderDir: string, dryRun: boolean, timeoutSeconds: number): CommandJsonResult {
return applyRuntimeLaneControlPlaneFiles(V02_LANE_SPEC, renderDir, dryRun, timeoutSeconds);
export function applyDefaultRuntimeLaneControlPlaneFiles(renderDir: string, dryRun: boolean, timeoutSeconds: number): CommandJsonResult {
return applyRuntimeLaneControlPlaneFiles(DEFAULT_RUNTIME_LANE_LANE_SPEC, renderDir, dryRun, timeoutSeconds);
}
export interface V02RefreshMarker {
export interface DefaultRuntimeLaneRefreshMarker {
ok: boolean;
sourceCommit: string;
refreshedAt: string;
@@ -210,42 +210,42 @@ export interface V02RefreshMarker {
renderDir?: string | null;
}
export function v02ControlPlaneRefreshStateDir(): string {
export function defaultRuntimeLaneControlPlaneRefreshStateDir(): string {
return rootPath(".state", "hwlab-g14", "v02-control-plane-refresh");
}
export function v02ControlPlaneRefreshScriptHash(sourceCommit: string): string {
return textHash(v02ControlPlaneRenderScript(sourceCommit, "contract-token"));
export function defaultRuntimeLaneControlPlaneRefreshScriptHash(sourceCommit: string): string {
return textHash(defaultRuntimeLaneControlPlaneRenderScript(sourceCommit, "contract-token"));
}
export function v02ControlPlaneRefreshMarkerPath(sourceCommit: string): string {
return join(v02ControlPlaneRefreshStateDir(), `${shortSha(sourceCommit)}.json`);
export function defaultRuntimeLaneControlPlaneRefreshMarkerPath(sourceCommit: string): string {
return join(defaultRuntimeLaneControlPlaneRefreshStateDir(), `${shortSha(sourceCommit)}.json`);
}
export function v02ControlPlaneRefreshLockDir(sourceCommit: string): string {
return join(v02ControlPlaneRefreshStateDir(), `${shortSha(sourceCommit)}.lock`);
export function defaultRuntimeLaneControlPlaneRefreshLockDir(sourceCommit: string): string {
return join(defaultRuntimeLaneControlPlaneRefreshStateDir(), `${shortSha(sourceCommit)}.lock`);
}
export function readV02RefreshMarker(sourceCommit: string, scriptHash: string, nowMs = Date.now()): V02RefreshMarker | null {
const path = v02ControlPlaneRefreshMarkerPath(sourceCommit);
export function readDefaultRuntimeLaneRefreshMarker(sourceCommit: string, scriptHash: string, nowMs = Date.now()): DefaultRuntimeLaneRefreshMarker | null {
const path = defaultRuntimeLaneControlPlaneRefreshMarkerPath(sourceCommit);
if (!existsSync(path)) return null;
try {
const marker = JSON.parse(readFileSync(path, "utf8")) as V02RefreshMarker;
return v02ReusableRefreshMarker(marker, sourceCommit, scriptHash, nowMs);
const marker = JSON.parse(readFileSync(path, "utf8")) as DefaultRuntimeLaneRefreshMarker;
return defaultRuntimeLaneReusableRefreshMarker(marker, sourceCommit, scriptHash, nowMs);
} catch {
return null;
}
}
export function v02ReusableRefreshMarker(marker: unknown, sourceCommit: string, scriptHash: string, nowMs = Date.now()): V02RefreshMarker | null {
const candidate = record(marker) as V02RefreshMarker;
export function defaultRuntimeLaneReusableRefreshMarker(marker: unknown, sourceCommit: string, scriptHash: string, nowMs = Date.now()): DefaultRuntimeLaneRefreshMarker | null {
const candidate = record(marker) as DefaultRuntimeLaneRefreshMarker;
const refreshedAtMs = timestampMs(candidate.refreshedAt);
if (candidate.ok !== true || candidate.sourceCommit !== sourceCommit || candidate.renderScriptHash !== scriptHash) return null;
if (refreshedAtMs === null || nowMs - refreshedAtMs > V02_CONTROL_PLANE_REFRESH_TTL_MS) return null;
if (refreshedAtMs === null || nowMs - refreshedAtMs > DEFAULT_RUNTIME_LANE_CONTROL_PLANE_REFRESH_TTL_MS) return null;
return candidate;
}
export function writeV02RefreshMarker(sourceCommit: string, scriptHash: string, renderDir: string | null): V02RefreshMarker {
export function writeDefaultRuntimeLaneRefreshMarker(sourceCommit: string, scriptHash: string, renderDir: string | null): DefaultRuntimeLaneRefreshMarker {
const marker = {
ok: true,
sourceCommit,
@@ -253,16 +253,16 @@ export function writeV02RefreshMarker(sourceCommit: string, scriptHash: string,
renderScriptHash: scriptHash,
renderDir,
};
const dir = v02ControlPlaneRefreshStateDir();
const dir = defaultRuntimeLaneControlPlaneRefreshStateDir();
mkdirSync(dir, { recursive: true });
writeFileSync(v02ControlPlaneRefreshMarkerPath(sourceCommit), `${JSON.stringify(marker, null, 2)}\n`, "utf8");
writeFileSync(defaultRuntimeLaneControlPlaneRefreshMarkerPath(sourceCommit), `${JSON.stringify(marker, null, 2)}\n`, "utf8");
return marker;
}
export function acquireV02RefreshLock(sourceCommit: string): { acquired: boolean; lockDir: string; waitedMs: number } {
const stateDir = v02ControlPlaneRefreshStateDir();
export function acquireDefaultRuntimeLaneRefreshLock(sourceCommit: string): { acquired: boolean; lockDir: string; waitedMs: number } {
const stateDir = defaultRuntimeLaneControlPlaneRefreshStateDir();
mkdirSync(stateDir, { recursive: true });
const lockDir = v02ControlPlaneRefreshLockDir(sourceCommit);
const lockDir = defaultRuntimeLaneControlPlaneRefreshLockDir(sourceCommit);
const startedAtMs = Date.now();
for (;;) {
try {
@@ -277,7 +277,7 @@ export function acquireV02RefreshLock(sourceCommit: string): { acquired: boolean
return 0;
}
})();
if (ageMs > V02_CONTROL_PLANE_REFRESH_LOCK_STALE_MS) {
if (ageMs > DEFAULT_RUNTIME_LANE_CONTROL_PLANE_REFRESH_LOCK_STALE_MS) {
try {
rmSync(lockDir, { recursive: true, force: true });
continue;
@@ -285,14 +285,14 @@ export function acquireV02RefreshLock(sourceCommit: string): { acquired: boolean
return { acquired: false, lockDir, waitedMs: Date.now() - startedAtMs };
}
}
if (Date.now() - startedAtMs >= V02_CONTROL_PLANE_REFRESH_TTL_MS) return { acquired: false, lockDir, waitedMs: Date.now() - startedAtMs };
if (Date.now() - startedAtMs >= DEFAULT_RUNTIME_LANE_CONTROL_PLANE_REFRESH_TTL_MS) return { acquired: false, lockDir, waitedMs: Date.now() - startedAtMs };
const wait = runCommand(["sleep", "1"], repoRoot, { timeoutMs: 2_000 });
if (wait.exitCode !== 0 && wait.timedOut) return { acquired: false, lockDir, waitedMs: Date.now() - startedAtMs };
}
}
}
export function releaseV02RefreshLock(lockDir: string): void {
export function releaseDefaultRuntimeLaneRefreshLock(lockDir: string): void {
try {
rmSync(lockDir, { recursive: true, force: true });
} catch {
@@ -300,9 +300,9 @@ export function releaseV02RefreshLock(lockDir: string): void {
}
}
export function refreshV02ControlPlaneBeforeTrigger(sourceCommit: string, timeoutSeconds: number): Record<string, unknown> {
const scriptHash = v02ControlPlaneRefreshScriptHash(sourceCommit);
const existingMarker = readV02RefreshMarker(sourceCommit, scriptHash);
export function refreshDefaultRuntimeLaneControlPlaneBeforeTrigger(sourceCommit: string, timeoutSeconds: number): Record<string, unknown> {
const scriptHash = defaultRuntimeLaneControlPlaneRefreshScriptHash(sourceCommit);
const existingMarker = readDefaultRuntimeLaneRefreshMarker(sourceCommit, scriptHash);
if (existingMarker !== null) {
return {
ok: true,
@@ -311,10 +311,10 @@ export function refreshV02ControlPlaneBeforeTrigger(sourceCommit: string, timeou
sourceCommit,
renderDir: existingMarker.renderDir ?? null,
marker: existingMarker,
markerTtlMs: V02_CONTROL_PLANE_REFRESH_TTL_MS,
markerTtlMs: DEFAULT_RUNTIME_LANE_CONTROL_PLANE_REFRESH_TTL_MS,
};
}
const lock = acquireV02RefreshLock(sourceCommit);
const lock = acquireDefaultRuntimeLaneRefreshLock(sourceCommit);
if (!lock.acquired) {
return {
ok: false,
@@ -327,7 +327,7 @@ export function refreshV02ControlPlaneBeforeTrigger(sourceCommit: string, timeou
};
}
try {
const markerAfterWait = readV02RefreshMarker(sourceCommit, scriptHash);
const markerAfterWait = readDefaultRuntimeLaneRefreshMarker(sourceCommit, scriptHash);
if (markerAfterWait !== null) {
return {
ok: true,
@@ -337,10 +337,10 @@ export function refreshV02ControlPlaneBeforeTrigger(sourceCommit: string, timeou
renderDir: markerAfterWait.renderDir ?? null,
marker: markerAfterWait,
waitedMs: lock.waitedMs,
markerTtlMs: V02_CONTROL_PLANE_REFRESH_TTL_MS,
markerTtlMs: DEFAULT_RUNTIME_LANE_CONTROL_PLANE_REFRESH_TTL_MS,
};
}
const render = runV02RenderToTemp(sourceCommit);
const render = runDefaultRuntimeLaneRenderToTemp(sourceCommit);
if (!isCommandSuccess(render.result)) {
return {
ok: false,
@@ -351,11 +351,11 @@ export function refreshV02ControlPlaneBeforeTrigger(sourceCommit: string, timeou
degradedReason: "control-plane-render-failed",
};
}
const apply = applyV02ControlPlaneFiles(render.renderDir, false, timeoutSeconds);
const cleanupObsoleteCronJobs = isCommandSuccess(apply) ? deleteV02ObsoleteCronJobs(false) : null;
const cleanupRenderDir = isCommandSuccess(apply) ? cleanupV02RenderDir(render.renderDir) : null;
const apply = applyDefaultRuntimeLaneControlPlaneFiles(render.renderDir, false, timeoutSeconds);
const cleanupObsoleteCronJobs = isCommandSuccess(apply) ? deleteDefaultRuntimeLaneObsoleteCronJobs(false) : null;
const cleanupRenderDir = isCommandSuccess(apply) ? cleanupDefaultRuntimeLaneRenderDir(render.renderDir) : null;
const ok = isCommandSuccess(apply) && (cleanupObsoleteCronJobs === null || isCommandSuccess(cleanupObsoleteCronJobs));
const marker = ok ? writeV02RefreshMarker(sourceCommit, scriptHash, render.renderDir) : null;
const marker = ok ? writeDefaultRuntimeLaneRefreshMarker(sourceCommit, scriptHash, render.renderDir) : null;
return {
ok,
phase: "control-plane-refresh",
@@ -375,34 +375,34 @@ export function refreshV02ControlPlaneBeforeTrigger(sourceCommit: string, timeou
: undefined,
};
} finally {
releaseV02RefreshLock(lock.lockDir);
releaseDefaultRuntimeLaneRefreshLock(lock.lockDir);
}
}
export function getV02ObsoleteCronJobs(): CommandJsonResult {
return g14K3s([
export function getDefaultRuntimeLaneObsoleteCronJobs(): CommandJsonResult {
return legacyRuntimeK3s([
"kubectl",
"get",
"cronjob",
"-n",
CI_NAMESPACE,
V02_POLLER,
V02_RECONCILER,
DEFAULT_RUNTIME_LANE_POLLER,
DEFAULT_RUNTIME_LANE_RECONCILER,
"--ignore-not-found",
"-o",
"name",
], 60_000);
}
export function deleteV02ObsoleteCronJobs(dryRun: boolean): CommandJsonResult {
return g14K3s([
export function deleteDefaultRuntimeLaneObsoleteCronJobs(dryRun: boolean): CommandJsonResult {
return legacyRuntimeK3s([
"kubectl",
"delete",
"cronjob",
"-n",
CI_NAMESPACE,
V02_POLLER,
V02_RECONCILER,
DEFAULT_RUNTIME_LANE_POLLER,
DEFAULT_RUNTIME_LANE_RECONCILER,
"--ignore-not-found=true",
...(dryRun ? ["--dry-run=server", "-o", "name"] : []),
], 60_000);
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. retirement module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. retirement module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:5525-5845 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:5525-5845 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -10,35 +10,35 @@ import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import type { G14LegacyRetirementOptions } from "./types";
import type { LegacyRuntimeLegacyRetirementOptions } from "./types";
import { k8sMetadataName, parseSectionJsonArray } from "./observability";
import { statusText } from "./pr-monitor";
import { compactCommandResult, g14K3s, isCommandSuccess, nested, record, shellQuote } from "./remote";
import { ARGO_NAMESPACE, DEV_APP, DEV_NAMESPACE, G14_PROVIDER, PROD_APP, PROD_NAMESPACE, V02_APP, V02_RUNTIME_NAMESPACE } from "./types";
import { parseShellSections, shellSectionOk } from "./v02-status";
import { compactCommandResult, legacyRuntimeK3s, isCommandSuccess, nested, record, shellQuote } from "./remote";
import { ARGO_NAMESPACE, DEV_APP, DEV_NAMESPACE, LEGACY_RUNTIME_PROVIDER, PROD_APP, PROD_NAMESPACE, DEFAULT_RUNTIME_LANE_APP, DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE } from "./types";
import { parseShellSections, shellSectionOk } from "./runtime-status";
export function legacyG14RetirementStatePath(): string {
export function legacyLegacyRuntimeRetirementStatePath(): string {
return rootPath(".state", "hwlab-g14", "legacy-g14-retirement.json");
}
export function legacyG14RetirementApplications(): string[] {
export function legacyLegacyRuntimeRetirementApplications(): string[] {
return [DEV_APP, PROD_APP];
}
export function legacyG14RetirementNamespaces(): string[] {
export function legacyLegacyRuntimeRetirementNamespaces(): string[] {
return [DEV_NAMESPACE, PROD_NAMESPACE];
}
export function protectedG14RuntimeApplications(): string[] {
return [V02_APP, hwlabRuntimeLaneSpec("v03").app];
export function protectedLegacyRuntimeRuntimeApplications(): string[] {
return [DEFAULT_RUNTIME_LANE_APP, hwlabRuntimeLaneSpec("v03").app];
}
export function protectedG14RuntimeNamespaces(): string[] {
return [V02_RUNTIME_NAMESPACE, hwlabRuntimeLaneSpec("v03").runtimeNamespace];
export function protectedLegacyRuntimeRuntimeNamespaces(): string[] {
return [DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE, hwlabRuntimeLaneSpec("v03").runtimeNamespace];
}
export function readLegacyG14RetirementMarker(): Record<string, unknown> | null {
const path = legacyG14RetirementStatePath();
export function readLegacyLegacyRuntimeRetirementMarker(): Record<string, unknown> | null {
const path = legacyLegacyRuntimeRetirementStatePath();
if (!existsSync(path)) return null;
try {
return record(JSON.parse(readFileSync(path, "utf8")) as unknown);
@@ -47,33 +47,33 @@ export function readLegacyG14RetirementMarker(): Record<string, unknown> | null
}
}
export function writeLegacyG14RetirementMarker(status: string, reason: string, details: Record<string, unknown> = {}): Record<string, unknown> {
const path = legacyG14RetirementStatePath();
export function writeLegacyLegacyRuntimeRetirementMarker(status: string, reason: string, details: Record<string, unknown> = {}): Record<string, unknown> {
const path = legacyLegacyRuntimeRetirementStatePath();
mkdirSync(dirname(path), { recursive: true });
const previous = readLegacyG14RetirementMarker();
const previous = readLegacyLegacyRuntimeRetirementMarker();
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(),
legacyApplications: legacyLegacyRuntimeRetirementApplications(),
legacyNamespaces: legacyLegacyRuntimeRetirementNamespaces(),
protectedApplications: protectedLegacyRuntimeRuntimeApplications(),
protectedNamespaces: protectedLegacyRuntimeRuntimeNamespaces(),
...details,
};
writeFileSync(path, `${JSON.stringify(marker, null, 2)}\n`, "utf8");
return { ...marker, path };
}
export function legacyG14RetirementBlocksMonitor(): Record<string, unknown> | null {
const marker = readLegacyG14RetirementMarker();
export function legacyLegacyRuntimeRetirementBlocksMonitor(): Record<string, unknown> | null {
const marker = readLegacyLegacyRuntimeRetirementMarker();
if (marker === null) {
return {
status: "retired-by-contract",
path: legacyG14RetirementStatePath(),
path: legacyLegacyRuntimeRetirementStatePath(),
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", v03Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v03" },
next: { status: "bun scripts/cli.ts hwlab g14 retirement status", defaultRuntimeLaneMonitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v02", v03Monitor: "bun scripts/cli.ts hwlab g14 monitor-prs --lane v03" },
};
}
const status = String(marker.status ?? "");
@@ -126,11 +126,11 @@ export function parseNamespacedResourcePreview(text: string): Record<string, unk
}));
}
export 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(" ");
export function legacyLegacyRuntimeRetirementStatusScript(): string {
const legacyApps = legacyLegacyRuntimeRetirementApplications().map(shellQuote).join(" ");
const legacyNamespaces = legacyLegacyRuntimeRetirementNamespaces().map(shellQuote).join(" ");
const protectedApps = protectedLegacyRuntimeRuntimeApplications().map(shellQuote).join(" ");
const protectedNamespaces = protectedLegacyRuntimeRuntimeNamespaces().map(shellQuote).join(" ");
return [
"set +e",
"section() {",
@@ -154,8 +154,8 @@ export function legacyG14RetirementStatusScript(): string {
].join("\n");
}
export function legacyG14RetirementStatus(): Record<string, unknown> {
const result = g14K3s(["sh", "--", legacyG14RetirementStatusScript()], 60_000);
export function legacyLegacyRuntimeRetirementStatus(): Record<string, unknown> {
const result = legacyRuntimeK3s(["sh", "--", legacyLegacyRuntimeRetirementStatusScript()], 60_000);
const sections = parseShellSections(statusText(result));
const legacyApplications = parseSectionJsonArray(sections.legacyApplications).map(applicationSummary);
const legacyNamespaces = parseSectionJsonArray(sections.legacyNamespaces).map(namespaceSummary);
@@ -167,8 +167,8 @@ export function legacyG14RetirementStatus(): Record<string, unknown> {
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 missingProtectedApplications = protectedLegacyRuntimeRuntimeApplications().filter((name) => !protectedAppNames.has(name));
const missingProtectedNamespaces = protectedLegacyRuntimeRuntimeNamespaces().filter((name) => !protectedNamespaceNames.has(name));
const sectionsOk =
shellSectionOk(sections.legacyApplications) &&
shellSectionOk(sections.legacyNamespaces) &&
@@ -178,7 +178,7 @@ export function legacyG14RetirementStatus(): Record<string, unknown> {
return {
ok: isCommandSuccess(result) && sectionsOk,
command: "hwlab g14 retirement status",
provider: G14_PROVIDER,
provider: LEGACY_RUNTIME_PROVIDER,
argoNamespace: ARGO_NAMESPACE,
retirementState,
retired,
@@ -194,15 +194,15 @@ export function legacyG14RetirementStatus(): Record<string, unknown> {
missingNamespaces: missingProtectedNamespaces,
ok: missingProtectedApplications.length === 0 && missingProtectedNamespaces.length === 0,
},
marker: readLegacyG14RetirementMarker(),
marker: readLegacyLegacyRuntimeRetirementMarker(),
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" }
? { verifyDefaultRuntimeLane: "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" },
};
}
export function legacyG14RetirementPlan(status = legacyG14RetirementStatus()): Record<string, unknown> {
export function legacyLegacyRuntimeRetirementPlan(status = legacyLegacyRuntimeRetirementStatus()): Record<string, unknown> {
return {
ok: status.ok === true,
command: "hwlab g14 retirement plan",
@@ -210,15 +210,15 @@ export function legacyG14RetirementPlan(status = legacyG14RetirementStatus()): R
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 })),
applications: legacyLegacyRuntimeRetirementApplications().map((name) => ({ apiVersion: "argoproj.io/v1alpha1", kind: "Application", namespace: ARGO_NAMESPACE, name })),
namespaces: legacyLegacyRuntimeRetirementNamespaces().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 })),
applications: protectedLegacyRuntimeRuntimeApplications().map((name) => ({ apiVersion: "argoproj.io/v1alpha1", kind: "Application", namespace: ARGO_NAMESPACE, name })),
namespaces: protectedLegacyRuntimeRuntimeNamespaces().map((name) => ({ apiVersion: "v1", kind: "Namespace", name })),
},
localSupportActions: [
{ action: "write-retirement-marker", path: legacyG14RetirementStatePath() },
{ action: "write-retirement-marker", path: legacyLegacyRuntimeRetirementStatePath() },
{ 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" },
],
@@ -227,23 +227,23 @@ export function legacyG14RetirementPlan(status = legacyG14RetirementStatus()): R
};
}
export function legacyG14RecordRolloutRetired(): Record<string, unknown> {
export function legacyLegacyRuntimeRecordRolloutRetired(): 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(),
retirement: readLegacyLegacyRuntimeRetirementMarker(),
next: {
retirementStatus: "bun scripts/cli.ts hwlab g14 retirement status",
v02Status: "bun scripts/cli.ts hwlab g14 control-plane status --lane v02",
defaultRuntimeLaneStatus: "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",
},
};
}
export function cancelLegacyG14MonitorJobs(dryRun: boolean): Record<string, unknown> {
export function cancelLegacyLegacyRuntimeMonitorJobs(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 }));
@@ -252,9 +252,9 @@ export function cancelLegacyG14MonitorJobs(dryRun: boolean): Record<string, unkn
return { ok: actions.every((action) => record(action).ok !== false), dryRun: false, candidates, actions };
}
export function legacyG14RetirementDeleteScript(): string {
const legacyApps = legacyG14RetirementApplications();
const legacyNamespaces = legacyG14RetirementNamespaces();
export function legacyLegacyRuntimeRetirementDeleteScript(): string {
const legacyApps = legacyLegacyRuntimeRetirementApplications();
const legacyNamespaces = legacyLegacyRuntimeRetirementNamespaces();
return [
"set +e",
"overall=0",
@@ -278,13 +278,13 @@ export function legacyG14RetirementDeleteScript(): string {
].join("\n");
}
export function waitForLegacyG14Retirement(timeoutSeconds: number): Record<string, unknown> {
export function waitForLegacyLegacyRuntimeRetirement(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();
lastStatus = legacyLegacyRuntimeRetirementStatus();
if (lastStatus.retired === true) {
return { ok: true, attempts, elapsedMs: Date.now() - startedAtMs, status: lastStatus };
}
@@ -294,21 +294,21 @@ export function waitForLegacyG14Retirement(timeoutSeconds: number): Record<strin
return { ok: false, attempts, elapsedMs: Date.now() - startedAtMs, status: lastStatus, degradedReason: "legacy-g14-retirement-not-complete-before-timeout" };
}
export function runLegacyG14Retirement(options: G14LegacyRetirementOptions): Record<string, unknown> {
const before = legacyG14RetirementStatus();
export function runLegacyLegacyRuntimeRetirement(options: LegacyRuntimeLegacyRetirementOptions): Record<string, unknown> {
const before = legacyLegacyRuntimeRetirementStatus();
if (options.action === "status") return before;
if (options.action === "plan" || options.dryRun) return { ...legacyG14RetirementPlan(before), command: `hwlab g14 retirement ${options.action}`, reason: options.reason };
if (options.action === "plan" || options.dryRun) return { ...legacyLegacyRuntimeRetirementPlan(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(["sh", "--", legacyG14RetirementDeleteScript()], Math.max(60_000, options.timeoutSeconds * 1000));
const markerBeforeDelete = writeLegacyLegacyRuntimeRetirementMarker("retiring", options.reason, { command: "hwlab g14 retirement execute --confirm" });
const localJobs = cancelLegacyLegacyRuntimeMonitorJobs(false);
const deleteResult = legacyRuntimeK3s(["sh", "--", legacyLegacyRuntimeRetirementDeleteScript()], Math.max(60_000, options.timeoutSeconds * 1000));
const deleteSections = parseShellSections(statusText(deleteResult));
const afterDelete = legacyG14RetirementStatus();
const wait = options.wait ? waitForLegacyG14Retirement(options.timeoutSeconds) : null;
const afterDelete = legacyLegacyRuntimeRetirementStatus();
const wait = options.wait ? waitForLegacyLegacyRuntimeRetirement(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, {
const marker = writeLegacyLegacyRuntimeRetirementMarker(finalRetired ? "retired" : liveMutationOk ? "retiring" : "failed", options.reason, {
command: "hwlab g14 retirement execute --confirm",
deleteExitCode: deleteResult.exitCode,
finalRetirementState: finalStatus.retirementState ?? null,
@@ -333,7 +333,7 @@ export function runLegacyG14Retirement(options: G14LegacyRetirementOptions): Rec
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" }
? { verifyDefaultRuntimeLane: "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" },
};
}
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. v02-status module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. v02-status module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:1309-2787 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:1309-2787 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -10,16 +10,16 @@ import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import type { CommandJsonResult, ShellSection, V02ControlPlaneStatusTarget, V02StatusTargetMode } from "./types";
import type { CommandJsonResult, ShellSection, DefaultRuntimeLaneControlPlaneStatusTarget, DefaultRuntimeLaneStatusTargetMode } from "./types";
import { commandErrorSummary } from "./cleanup";
import { gitMirrorCacheProbeScript } from "./git-mirror";
import { conditionStatus } from "./observability";
import { durationSeconds, statusText } from "./pr-monitor";
import { fieldBoolean, g14K3s, isCommandSuccess, nested, record, redactedCommand, shellQuote, shortSha, stringOrNull } from "./remote";
import { v02PipelineRunName } from "./source";
import { ARGO_NAMESPACE, CI_NAMESPACE, GIT_MIRROR_NAMESPACE, V02_APP, V02_BUILD_TASKRUN_CRITICAL_SECONDS, V02_BUILD_TASKRUN_WARNING_SECONDS, V02_CICD_REPO, V02_CLOUD_API_URL, V02_CLOUD_WEB_URL, V02_PIPELINERUN_PREFIX, V02_POLLER, V02_RECONCILER, V02_WORKSPACE } from "./types";
import { fieldBoolean, legacyRuntimeK3s, isCommandSuccess, nested, record, redactedCommand, shellQuote, shortSha, stringOrNull } from "./remote";
import { defaultRuntimeLanePipelineRunName } from "./source";
import { ARGO_NAMESPACE, CI_NAMESPACE, GIT_MIRROR_NAMESPACE, DEFAULT_RUNTIME_LANE_APP, DEFAULT_RUNTIME_LANE_BUILD_TASKRUN_CRITICAL_SECONDS, DEFAULT_RUNTIME_LANE_BUILD_TASKRUN_WARNING_SECONDS, DEFAULT_RUNTIME_LANE_CICD_REPO, DEFAULT_RUNTIME_LANE_CLOUD_API_URL, DEFAULT_RUNTIME_LANE_CLOUD_WEB_URL, DEFAULT_RUNTIME_LANE_PIPELINERUN_PREFIX, DEFAULT_RUNTIME_LANE_POLLER, DEFAULT_RUNTIME_LANE_RECONCILER, DEFAULT_RUNTIME_LANE_WORKSPACE } from "./types";
export interface V02TriggerSnapshot {
export interface DefaultRuntimeLaneTriggerSnapshot {
sourceCommit: string | null;
pipelineRun: string | null;
before: Record<string, unknown> | null;
@@ -27,7 +27,7 @@ export interface V02TriggerSnapshot {
observedAtMs: number;
}
export function parseV02TriggerSnapshot(result: CommandJsonResult, observedAtMs: number): V02TriggerSnapshot {
export function parseDefaultRuntimeLaneTriggerSnapshot(result: CommandJsonResult, observedAtMs: number): DefaultRuntimeLaneTriggerSnapshot {
const output = String(nested(result.parsed, ["data", "stdout"]) ?? result.stdout);
const fields = keyValueLinesFromText(output);
const sourceCommit = stringOrNull(fields.sourceCommit?.match(/^[0-9a-f]{40}$/iu)?.[0]);
@@ -52,13 +52,13 @@ export function parseV02TriggerSnapshot(result: CommandJsonResult, observedAtMs:
};
}
export function getV02TriggerSnapshot(): V02TriggerSnapshot {
export function getDefaultRuntimeLaneTriggerSnapshot(): DefaultRuntimeLaneTriggerSnapshot {
const script = [
"set +e",
"one_line() { tr '\\r\\n\\t' ' ' | sed 's/[[:space:]][[:space:]]*/ /g' | cut -c1-2000; }",
"tmp_dir=$(mktemp -d /tmp/hwlab-v02-trigger-snapshot.XXXXXX)",
"trap 'rm -rf \"$tmp_dir\"' EXIT",
`cicd_repo=${shellQuote(V02_CICD_REPO)}`,
`cicd_repo=${shellQuote(DEFAULT_RUNTIME_LANE_CICD_REPO)}`,
"source_commit=",
"if [ -d \"$cicd_repo/objects\" ] && [ -f \"$cicd_repo/HEAD\" ]; then",
" source_commit=$(git --git-dir=\"$cicd_repo\" rev-parse refs/remotes/origin/v0.2 2>/dev/null || true)",
@@ -66,7 +66,7 @@ export function getV02TriggerSnapshot(): V02TriggerSnapshot {
" printf 'snapshotError\\t%s\\n' \"v0.2 CI/CD repo path exists but is not a bare git repo: $cicd_repo\"",
"fi",
"pipeline_run=",
`if [ -n "$source_commit" ]; then pipeline_run=${shellQuote(V02_PIPELINERUN_PREFIX)}-$(printf '%s' "$source_commit" | cut -c1-12); fi`,
`if [ -n "$source_commit" ]; then pipeline_run=${shellQuote(DEFAULT_RUNTIME_LANE_PIPELINERUN_PREFIX)}-$(printf '%s' "$source_commit" | cut -c1-12); fi`,
"if [ -n \"$pipeline_run\" ]; then",
` (kubectl get pipelinerun -n ${shellQuote(CI_NAMESPACE)} "$pipeline_run" -o 'jsonpath={.status.conditions[0].status}{"\\n"}{.status.conditions[0].reason}{"\\n"}{.status.conditions[0].message}{"\\n"}' >\"$tmp_dir/pipelinerun.out\" 2>\"$tmp_dir/pipelinerun.err\"; printf '%s' "$?" >\"$tmp_dir/pipelinerun.status\") &`,
" pipeline_pid=$!",
@@ -87,11 +87,11 @@ export function getV02TriggerSnapshot(): V02TriggerSnapshot {
"printf 'message\\t%s\\n' \"$(printf '%s\\n' \"$status_text\" | sed -n '3p')\"",
"printf 'pipelineRunStderr\\t%s\\n' \"$(one_line < \"$tmp_dir/pipelinerun.err\")\"",
].join("\n");
const result = g14K3s(["sh", "--", script], 180_000);
return parseV02TriggerSnapshot(result, Date.now());
const result = legacyRuntimeK3s(["sh", "--", script], 180_000);
return parseDefaultRuntimeLaneTriggerSnapshot(result, Date.now());
}
export function v02ExistingPipelineRunReuseDecision(input: {
export function defaultRuntimeLaneExistingPipelineRunReuseDecision(input: {
sourceCommit: string;
before: Record<string, unknown>;
latestSourceCommit: string | null;
@@ -107,7 +107,7 @@ export function v02ExistingPipelineRunReuseDecision(input: {
reason: "source-head-recheck-unresolved-before-existing-pipelinerun-reuse",
sourceCommit: input.sourceCommit,
latestSourceCommit,
previousPipelineRun: v02PipelineRunName(input.sourceCommit),
previousPipelineRun: defaultRuntimeLanePipelineRunName(input.sourceCommit),
};
}
if (latestSourceCommit !== null && latestSourceCommit !== input.sourceCommit) {
@@ -117,8 +117,8 @@ export function v02ExistingPipelineRunReuseDecision(input: {
reason: "source-head-advanced-before-existing-pipelinerun-reuse",
sourceCommit: input.sourceCommit,
latestSourceCommit,
previousPipelineRun: v02PipelineRunName(input.sourceCommit),
latestPipelineRun: v02PipelineRunName(latestSourceCommit),
previousPipelineRun: defaultRuntimeLanePipelineRunName(input.sourceCommit),
latestPipelineRun: defaultRuntimeLanePipelineRunName(latestSourceCommit),
};
}
return {
@@ -127,12 +127,12 @@ export function v02ExistingPipelineRunReuseDecision(input: {
reason: alreadyUsable ? "existing-pipelinerun-reused" : "existing-pipelinerun-terminal-failed",
sourceCommit: input.sourceCommit,
latestSourceCommit,
previousPipelineRun: v02PipelineRunName(input.sourceCommit),
previousPipelineRun: defaultRuntimeLanePipelineRunName(input.sourceCommit),
};
}
export function getPipelineRunCompact(name: string): Record<string, unknown> {
const result = g14K3s([
const result = legacyRuntimeK3s([
"kubectl",
"get",
"pipelinerun",
@@ -336,7 +336,7 @@ export function secondsBetween(start: unknown, end: unknown): number | null {
return Math.round((endMs - startMs) / 1000);
}
export function pipelineRunStatusRowFromLine(line: string, nowMs: number, pipelineRunPrefix = V02_PIPELINERUN_PREFIX): Record<string, unknown> | null {
export function pipelineRunStatusRowFromLine(line: string, nowMs: number, pipelineRunPrefix = DEFAULT_RUNTIME_LANE_PIPELINERUN_PREFIX): Record<string, unknown> | null {
const [
name = "",
sourceCommit = "",
@@ -364,7 +364,7 @@ export function pipelineRunStatusRowFromLine(line: string, nowMs: number, pipeli
};
}
export function parsePipelineRunRows(text: string, limit: number, nowMs = Date.now(), pipelineRunPrefix = V02_PIPELINERUN_PREFIX): Record<string, unknown> {
export function parsePipelineRunRows(text: string, limit: number, nowMs = Date.now(), pipelineRunPrefix = DEFAULT_RUNTIME_LANE_PIPELINERUN_PREFIX): Record<string, unknown> {
const rows = text
.split(/\r?\n/u)
.map((line) => pipelineRunStatusRowFromLine(line.trim(), nowMs, pipelineRunPrefix))
@@ -404,8 +404,8 @@ export function pipelineRunRowsJsonPath(): string {
].join("");
}
export function listV02PipelineRunsCompact(limit = 8): Record<string, unknown> {
const result = g14K3s([
export function listDefaultRuntimeLanePipelineRunsCompact(limit = 8): Record<string, unknown> {
const result = legacyRuntimeK3s([
"kubectl",
"get",
"pipelinerun",
@@ -469,8 +469,8 @@ export function shellSectionOk(section: ShellSection | undefined): boolean {
return section?.exitCode === 0;
}
export function v02ControlPlaneStatusBundle(target: V02ControlPlaneStatusTarget = {}): CommandJsonResult {
const targetMode: V02StatusTargetMode = target.mode
export function defaultRuntimeLaneControlPlaneStatusBundle(target: DefaultRuntimeLaneControlPlaneStatusTarget = {}): CommandJsonResult {
const targetMode: DefaultRuntimeLaneStatusTargetMode = target.mode
?? (target.pipelineRun !== undefined && target.pipelineRun !== null ? "pipeline-run" : target.sourceCommit !== undefined ? "source-commit" : "latest-source-head");
const targetInit = target.pipelineRun !== undefined && target.pipelineRun !== null
? [
@@ -480,7 +480,7 @@ export function v02ControlPlaneStatusBundle(target: V02ControlPlaneStatusTarget
].join("\n")
: [
target.sourceCommit === undefined
? `source_commit=$(git --git-dir=${shellQuote(V02_CICD_REPO)} rev-parse refs/remotes/origin/v0.2 2>/dev/null || true)`
? `source_commit=$(git --git-dir=${shellQuote(DEFAULT_RUNTIME_LANE_CICD_REPO)} rev-parse refs/remotes/origin/v0.2 2>/dev/null || true)`
: `source_commit=${shellQuote(target.sourceCommit ?? "")}`,
"pipeline_run=",
].join("\n");
@@ -488,7 +488,7 @@ export function v02ControlPlaneStatusBundle(target: V02ControlPlaneStatusTarget
"set +e",
targetInit,
`target_mode=${shellQuote(targetMode)}`,
`if [ -z "$pipeline_run" ] && [ -n "$source_commit" ]; then pipeline_run="${V02_PIPELINERUN_PREFIX}-$(printf '%s' "$source_commit" | cut -c1-12)"; fi`,
`if [ -z "$pipeline_run" ] && [ -n "$source_commit" ]; then pipeline_run="${DEFAULT_RUNTIME_LANE_PIPELINERUN_PREFIX}-$(printf '%s' "$source_commit" | cut -c1-12)"; fi`,
"section() {",
" name=\"$1\"",
" shift",
@@ -500,28 +500,28 @@ export function v02ControlPlaneStatusBundle(target: V02ControlPlaneStatusTarget
"section statusTarget printf 'mode\\t%s\\nsourceCommit\\t%s\\npipelineRun\\t%s\\n' \"$target_mode\" \"$source_commit\" \"$pipeline_run\"",
"section sourceCommit printf '%s\\n' \"$source_commit\"",
"section pipelineRunName printf '%s\\n' \"$pipeline_run\"",
`section sourceHeads sh -c ${shellQuote(v02SourceHeadsProbeScript())} sh "$source_commit"`,
`section sourceHeads sh -c ${shellQuote(defaultRuntimeLaneSourceHeadsProbeScript())} sh "$source_commit"`,
"section queryNow date -u +%Y-%m-%dT%H:%M:%SZ",
`section controlPlane kubectl get pipeline,role,rolebinding,serviceaccount -n ${shellQuote(CI_NAMESPACE)} -l hwlab.pikastech.local/gitops-target=v02 -o name`,
`section obsoleteCronJobs kubectl get cronjob -n ${shellQuote(CI_NAMESPACE)} ${shellQuote(V02_POLLER)} ${shellQuote(V02_RECONCILER)} --ignore-not-found -o name`,
`section argo kubectl get application -n ${shellQuote(ARGO_NAMESPACE)} ${shellQuote(V02_APP)} -o 'jsonpath={.spec.source.targetRevision}{"\\n"}{.spec.source.path}{"\\n"}{.status.sync.revision}{"\\n"}{.status.sync.status}{"\\n"}{.status.health.status}{"\\n"}'`,
`section obsoleteCronJobs kubectl get cronjob -n ${shellQuote(CI_NAMESPACE)} ${shellQuote(DEFAULT_RUNTIME_LANE_POLLER)} ${shellQuote(DEFAULT_RUNTIME_LANE_RECONCILER)} --ignore-not-found -o name`,
`section argo kubectl get application -n ${shellQuote(ARGO_NAMESPACE)} ${shellQuote(DEFAULT_RUNTIME_LANE_APP)} -o 'jsonpath={.spec.source.targetRevision}{"\\n"}{.spec.source.path}{"\\n"}{.status.sync.revision}{"\\n"}{.status.sync.status}{"\\n"}{.status.health.status}{"\\n"}'`,
`if [ -n "$pipeline_run" ]; then section pipelineRun kubectl get pipelinerun -n ${shellQuote(CI_NAMESPACE)} "$pipeline_run" -o 'jsonpath={.status.conditions[0].status}{"\\n"}{.status.conditions[0].reason}{"\\n"}{.status.conditions[0].message}{"\\n"}'; else section pipelineRun sh -c 'true'; fi`,
`if [ -n "$pipeline_run" ]; then section taskRuns kubectl get taskrun -n ${shellQuote(CI_NAMESPACE)} -l "tekton.dev/pipelineRun=$pipeline_run" -o 'jsonpath={range .items[*]}{.metadata.name}{"\\t"}{.status.conditions[0].status}{"\\t"}{.status.conditions[0].reason}{"\\t"}{.status.startTime}{"\\t"}{.status.completionTime}{"\\n"}{end}'; else section taskRuns sh -c 'true'; fi`,
`if [ -n "$pipeline_run" ]; then section planArtifacts sh -c ${shellQuote(v02PlanArtifactsLogScript())} plan-artifacts "$pipeline_run"; else section planArtifacts sh -c 'true'; fi`,
`section runtimeWorkloads kubectl get deploy,statefulset,job -n hwlab-v02 -l hwlab.pikastech.local/gitops-target=v02 -o ${shellQuote(v02RuntimeWorkloadsColumns())} --no-headers`,
`if [ -n "$pipeline_run" ]; then section planArtifacts sh -c ${shellQuote(defaultRuntimeLanePlanArtifactsLogScript())} plan-artifacts "$pipeline_run"; else section planArtifacts sh -c 'true'; fi`,
`section runtimeWorkloads kubectl get deploy,statefulset,job -n hwlab-v02 -l hwlab.pikastech.local/gitops-target=v02 -o ${shellQuote(defaultRuntimeLaneRuntimeWorkloadsColumns())} --no-headers`,
`section recentPipelineRuns kubectl get pipelinerun -n ${shellQuote(CI_NAMESPACE)} -l hwlab.pikastech.local/gitops-target=v02 -o ${shellQuote(pipelineRunRowsJsonPath())}`,
`section gitMirrorCache kubectl exec -n ${shellQuote(GIT_MIRROR_NAMESPACE)} deploy/git-mirror-http -- sh -lc ${shellQuote(gitMirrorCacheProbeScript())}`,
`section webAssets sh -c ${shellQuote(v02WebAssetsProbeScript())}`,
`section webAssets sh -c ${shellQuote(defaultRuntimeLaneWebAssetsProbeScript())}`,
].join("\n");
return g14K3s(["sh", "--", script], 60_000);
return legacyRuntimeK3s(["sh", "--", script], 60_000);
}
export function v02SourceHeadsProbeScript(): string {
export function defaultRuntimeLaneSourceHeadsProbeScript(): string {
return [
"set +e",
"target_source_commit=${1:-}",
`cicd_repo=${shellQuote(V02_CICD_REPO)}`,
`workspace=${shellQuote(V02_WORKSPACE)}`,
`cicd_repo=${shellQuote(DEFAULT_RUNTIME_LANE_CICD_REPO)}`,
`workspace=${shellQuote(DEFAULT_RUNTIME_LANE_WORKSPACE)}`,
"rev_cicd() { git --git-dir=\"$cicd_repo\" rev-parse \"$1\" 2>/dev/null || true; }",
"rev_workspace() { git -C \"$workspace\" rev-parse \"$1\" 2>/dev/null || true; }",
"origin_head=$(rev_cicd refs/remotes/origin/v0.2)",
@@ -554,7 +554,7 @@ export function v02SourceHeadsProbeScript(): string {
].join("\n");
}
export function v02PlanArtifactsLogScript(): string {
export function defaultRuntimeLanePlanArtifactsLogScript(): string {
return [
"pipeline_run=\"$1\"",
`namespace=${shellQuote(CI_NAMESPACE)}`,
@@ -567,7 +567,7 @@ export function v02PlanArtifactsLogScript(): string {
].join("\n");
}
export function v02RuntimeWorkloadsColumns(): string {
export function defaultRuntimeLaneRuntimeWorkloadsColumns(): string {
return [
"custom-columns=KIND:.kind",
"NAME:.metadata.name",
@@ -580,11 +580,11 @@ export function v02RuntimeWorkloadsColumns(): string {
].join(",");
}
export function v02WebAssetsProbeScript(): string {
export function defaultRuntimeLaneWebAssetsProbeScript(): string {
return [
"set +e",
`base=${shellQuote(V02_CLOUD_WEB_URL)}`,
`api=${shellQuote(V02_CLOUD_API_URL)}`,
`base=${shellQuote(DEFAULT_RUNTIME_LANE_CLOUD_WEB_URL)}`,
`api=${shellQuote(DEFAULT_RUNTIME_LANE_CLOUD_API_URL)}`,
"fetch_url() {",
" if command -v curl >/dev/null 2>&1; then",
" curl -fsS --connect-timeout 2 --max-time 10 \"$1\"",
@@ -642,7 +642,7 @@ export function taskRunsCompactFromText(text: string, commandOk: boolean, pipeli
stderr: stderr.trim().slice(0, 2000),
counts: { succeeded: 0, failed: 0, running: 0, unknown: 0 },
items: [],
performance: v02TaskRunPerformanceSummary([]),
performance: defaultRuntimeLaneTaskRunPerformanceSummary([]),
};
}
const items = text
@@ -666,7 +666,7 @@ export function taskRunsCompactFromText(text: string, commandOk: boolean, pipeli
running: items.filter((item) => item.status === "Unknown").length,
unknown: items.filter((item) => item.status !== "True" && item.status !== "False" && item.status !== "Unknown").length,
};
const performance = v02TaskRunPerformanceSummary(items);
const performance = defaultRuntimeLaneTaskRunPerformanceSummary(items);
const performanceWarning = performance.ok === false ? `; ${String(performance.summary ?? "")}` : "";
return {
ok: true,
@@ -679,7 +679,7 @@ export function taskRunsCompactFromText(text: string, commandOk: boolean, pipeli
};
}
export function v02TaskRunPipelineTaskName(name: string): string | null {
export function defaultRuntimeLaneTaskRunPipelineTaskName(name: string): string | null {
const buildIndex = name.lastIndexOf("-build-");
if (buildIndex >= 0) return name.slice(buildIndex + 1);
const knownSuffixes = [
@@ -694,14 +694,14 @@ export function v02TaskRunPipelineTaskName(name: string): string | null {
return knownSuffixes.find((suffix) => name.endsWith(`-${suffix}`)) ?? null;
}
export function v02TaskRunPerformanceSummary(taskRuns: unknown[]): Record<string, unknown> {
export function defaultRuntimeLaneTaskRunPerformanceSummary(taskRuns: unknown[]): Record<string, unknown> {
const slowTaskRuns: Record<string, unknown>[] = [];
for (const itemRaw of taskRuns) {
const item = record(itemRaw);
const name = stringOrNull(item.name) ?? "";
const pipelineTask = stringOrNull(item.pipelineTask) ?? v02TaskRunPipelineTaskName(name);
const pipelineTask = stringOrNull(item.pipelineTask) ?? defaultRuntimeLaneTaskRunPipelineTaskName(name);
const durationSeconds = typeof item.durationSeconds === "number" ? item.durationSeconds : null;
if (!pipelineTask?.startsWith("build-") || durationSeconds === null || durationSeconds <= V02_BUILD_TASKRUN_WARNING_SECONDS) continue;
if (!pipelineTask?.startsWith("build-") || durationSeconds === null || durationSeconds <= DEFAULT_RUNTIME_LANE_BUILD_TASKRUN_WARNING_SECONDS) continue;
const serviceId = pipelineTask.slice("build-".length) || null;
slowTaskRuns.push({
name,
@@ -710,9 +710,9 @@ export function v02TaskRunPerformanceSummary(taskRuns: unknown[]): Record<string
status: stringOrNull(item.status),
reason: stringOrNull(item.reason),
durationSeconds,
budgetSeconds: V02_BUILD_TASKRUN_WARNING_SECONDS,
severity: durationSeconds >= V02_BUILD_TASKRUN_CRITICAL_SECONDS ? "critical" : "warning",
message: `${pipelineTask} took ${durationSeconds}s, above v0.2 build TaskRun warning budget ${V02_BUILD_TASKRUN_WARNING_SECONDS}s`,
budgetSeconds: DEFAULT_RUNTIME_LANE_BUILD_TASKRUN_WARNING_SECONDS,
severity: durationSeconds >= DEFAULT_RUNTIME_LANE_BUILD_TASKRUN_CRITICAL_SECONDS ? "critical" : "warning",
message: `${pipelineTask} took ${durationSeconds}s, above v0.2 build TaskRun warning budget ${DEFAULT_RUNTIME_LANE_BUILD_TASKRUN_WARNING_SECONDS}s`,
});
}
slowTaskRuns.sort((left, right) => Number(right.durationSeconds ?? 0) - Number(left.durationSeconds ?? 0));
@@ -721,17 +721,17 @@ export function v02TaskRunPerformanceSummary(taskRuns: unknown[]): Record<string
ok: slowTaskRuns.length === 0,
warningCount: slowTaskRuns.length,
thresholds: {
buildTaskRunWarningSeconds: V02_BUILD_TASKRUN_WARNING_SECONDS,
buildTaskRunCriticalSeconds: V02_BUILD_TASKRUN_CRITICAL_SECONDS,
buildTaskRunWarningSeconds: DEFAULT_RUNTIME_LANE_BUILD_TASKRUN_WARNING_SECONDS,
buildTaskRunCriticalSeconds: DEFAULT_RUNTIME_LANE_BUILD_TASKRUN_CRITICAL_SECONDS,
},
slowTaskRuns,
summary: worst
? `slow build taskruns=${slowTaskRuns.length}; worst=${String(worst.pipelineTask)} ${String(worst.durationSeconds)}s budget=${V02_BUILD_TASKRUN_WARNING_SECONDS}s`
: `no build taskrun over ${V02_BUILD_TASKRUN_WARNING_SECONDS}s`,
? `slow build taskruns=${slowTaskRuns.length}; worst=${String(worst.pipelineTask)} ${String(worst.durationSeconds)}s budget=${DEFAULT_RUNTIME_LANE_BUILD_TASKRUN_WARNING_SECONDS}s`
: `no build taskrun over ${DEFAULT_RUNTIME_LANE_BUILD_TASKRUN_WARNING_SECONDS}s`,
};
}
export function v02WebAssetsFromText(
export function defaultRuntimeLaneWebAssetsFromText(
text: string,
commandOk: boolean,
sourceCommit: string | null,
@@ -770,8 +770,8 @@ export function v02WebAssetsFromText(
return {
ok: commandOk && webChecksPass,
summary: commandOk && webChecksPass ? "19666/19667 React/Vite asset probes passed" : `19666/19667 probe issues: ${failedChecks.join(", ") || "command failed"}`,
baseUrl: fields.baseUrl || V02_CLOUD_WEB_URL,
apiUrl: fields.apiUrl || V02_CLOUD_API_URL,
baseUrl: fields.baseUrl || DEFAULT_RUNTIME_LANE_CLOUD_WEB_URL,
apiUrl: fields.apiUrl || DEFAULT_RUNTIME_LANE_CLOUD_API_URL,
sourceCommit,
argoSyncRevision: argoSyncRevision || null,
cssPath: fields.cssPath || null,
@@ -809,7 +809,7 @@ export function v02WebAssetsFromText(
};
}
export function v02PlanArtifactsFromText(text: string, commandOk: boolean, pipelineRun: string | null, exitCode: number | null, stderr: string): Record<string, unknown> {
export function defaultRuntimeLanePlanArtifactsFromText(text: string, commandOk: boolean, pipelineRun: string | null, exitCode: number | null, stderr: string): Record<string, unknown> {
const events: Record<string, unknown>[] = [];
const pods: string[] = [];
for (const rawLine of text.split(/\r?\n/u)) {
@@ -858,7 +858,7 @@ export function v02PlanArtifactsFromText(text: string, commandOk: boolean, pipel
};
}
export function v02RuntimeWorkloadsFromText(text: string, commandOk: boolean, exitCode: number | null, stderr: string): Record<string, unknown> {
export function defaultRuntimeLaneRuntimeWorkloadsFromText(text: string, commandOk: boolean, exitCode: number | null, stderr: string): Record<string, unknown> {
if (!commandOk) {
return {
ok: false,
@@ -900,13 +900,13 @@ export function keyValueLinesFromText(text: string): Record<string, string> {
return fields;
}
export function v02SourceHeadsFromText(text: string, commandOk: boolean, exitCode: number | null, stderr: string): Record<string, unknown> {
export function defaultRuntimeLaneSourceHeadsFromText(text: string, commandOk: boolean, exitCode: number | null, stderr: string): Record<string, unknown> {
const fields = keyValueLinesFromText(text);
const dirtyCount = numericField(fields.workspaceDirtyCount);
const targetSourceCommit = fields.targetSourceCommit || null;
return {
ok: commandOk,
cicdRepo: fields.cicdRepo || V02_CICD_REPO,
cicdRepo: fields.cicdRepo || DEFAULT_RUNTIME_LANE_CICD_REPO,
cicdRepoExists: fields.cicdRepoExists === "yes",
cicdSourceHead: fields.cicdSourceHead || null,
originHead: fields.originHead || null,
@@ -919,7 +919,7 @@ export function v02SourceHeadsFromText(text: string, commandOk: boolean, exitCod
targetObjectExists: fieldBoolean(fields.targetObjectExists),
targetAncestorOfOriginHead: fieldBoolean(fields.targetAncestorOfOriginHead),
workspace: {
path: fields.workspacePath || V02_WORKSPACE,
path: fields.workspacePath || DEFAULT_RUNTIME_LANE_WORKSPACE,
branch: fields.workspaceBranch || null,
head: fields.workspaceHead || null,
originHead: fields.workspaceOriginHead || null,
@@ -932,7 +932,7 @@ export function v02SourceHeadsFromText(text: string, commandOk: boolean, exitCod
};
}
export function v02FalseGreenGuard(input: {
export function defaultRuntimeLaneFalseGreenGuard(input: {
sourceCommit: string | null;
pipelineRun: Record<string, unknown> | null;
taskRuns: Record<string, unknown>;
@@ -1008,8 +1008,8 @@ export function v02FalseGreenGuard(input: {
};
}
export function v02TargetValidation(input: {
targetMode: V02StatusTargetMode;
export function defaultRuntimeLaneTargetValidation(input: {
targetMode: DefaultRuntimeLaneStatusTargetMode;
sourceCommit: string | null;
pipelineRun: Record<string, unknown> | null;
argo: Record<string, unknown>;
@@ -1112,7 +1112,7 @@ export function v02TargetValidation(input: {
};
}
export function v02LatestOnlyTargetValidation(input: {
export function defaultRuntimeLaneLatestOnlyTargetValidation(input: {
targetMode: string;
sourceCommit: string | null;
pipelineRun: Record<string, unknown> | null;
@@ -1147,7 +1147,7 @@ export function v02LatestOnlyTargetValidation(input: {
};
}
export function v02CloseoutRecommendedNext(input: {
export function defaultRuntimeLaneCloseoutRecommendedNext(input: {
closeable: boolean;
blockedReasons: string[];
pendingReasons: string[];
@@ -1180,7 +1180,7 @@ export function v02CloseoutRecommendedNext(input: {
return { action: "inspect-status", command: input.statusCommand };
}
export function v02CloseoutVerdict(status: Record<string, unknown>): Record<string, unknown> {
export function defaultRuntimeLaneCloseoutVerdict(status: Record<string, unknown>): Record<string, unknown> {
const statusTarget = record(status.statusTarget);
const targetMode = stringOrNull(statusTarget.mode) ?? "latest-source-head";
const sourceCommit = stringOrNull(status.sourceCommit) ?? stringOrNull(statusTarget.sourceCommit);
@@ -1259,7 +1259,7 @@ export function v02CloseoutVerdict(status: Record<string, unknown>): Record<stri
? null
: `trans G14:/root/hwlab-v02 sh -- 'git fetch origin v0.2 && git merge-base --is-ancestor ${sourceCommit} origin/v0.2'`,
};
const recommendedNext = v02CloseoutRecommendedNext({
const recommendedNext = defaultRuntimeLaneCloseoutRecommendedNext({
closeable,
blockedReasons: uniqueBlockedReasons,
pendingReasons: uniquePendingReasons,
@@ -1341,7 +1341,7 @@ export function v02CloseoutVerdict(status: Record<string, unknown>): Record<stri
};
}
export function v02CommitAlignment(input: {
export function defaultRuntimeLaneCommitAlignment(input: {
expectedSourceHead: string | null;
sourceHeads: Record<string, unknown>;
gitMirrorSummary: Record<string, unknown>;
@@ -1357,8 +1357,8 @@ export function v02CommitAlignment(input: {
const workspace = record(input.sourceHeads.workspace);
const workspaceHead = stringOrNull(workspace.head);
const workspaceOriginHead = stringOrNull(workspace.originHead);
const mirrorSourceHead = stringOrNull(input.gitMirrorSummary.localV02);
const mirrorGithubSourceHead = stringOrNull(input.gitMirrorSummary.githubV02);
const mirrorSourceHead = stringOrNull(input.gitMirrorSummary.localDefaultRuntimeLane);
const mirrorGithubSourceHead = stringOrNull(input.gitMirrorSummary.githubDefaultRuntimeLane);
const recentItems = Array.isArray(input.recentPipelineRuns.items)
? input.recentPipelineRuns.items.map((item) => record(item))
: [];
@@ -1406,7 +1406,7 @@ export function v02CommitAlignment(input: {
expectedSourceHead,
originHead,
cicdSourceHead,
cicdRepo: input.sourceHeads.cicdRepo ?? V02_CICD_REPO,
cicdRepo: input.sourceHeads.cicdRepo ?? DEFAULT_RUNTIME_LANE_CICD_REPO,
mirrorSourceHead,
mirrorGithubSourceHead,
latestPipelineRun: latestPipelineRun === null
@@ -1437,7 +1437,7 @@ export function v02CommitAlignment(input: {
gitopsInSync: input.gitMirrorSummary.gitopsInSync ?? null,
staleReasons,
workspace: {
path: workspace.path ?? V02_WORKSPACE,
path: workspace.path ?? DEFAULT_RUNTIME_LANE_WORKSPACE,
branch: workspace.branch ?? null,
head: workspaceHead,
originHead: workspaceOriginHead,
@@ -1484,7 +1484,7 @@ export function stringArray(value: unknown): string[] {
return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string" && item.length > 0) : [];
}
export function listV02PipelineRunsCompactFromText(text: string, commandOk: boolean, command: string[] | string, exitCode: number | null, stderr: string, limit = 8, nowMs = Date.now()): Record<string, unknown> {
export function listDefaultRuntimeLanePipelineRunsCompactFromText(text: string, commandOk: boolean, command: string[] | string, exitCode: number | null, stderr: string, limit = 8, nowMs = Date.now()): Record<string, unknown> {
if (!commandOk) {
return {
ok: false,
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. secrets module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. secrets module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:5055-5524 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:5055-5524 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -10,25 +10,25 @@ import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import type { G14SecretOptions } from "./types";
import type { LegacyRuntimeSecretOptions } from "./types";
import { statusText } from "./pr-monitor";
import { compactCommandResult, g14K3s, g14K3sInlineScriptWithInput, isCommandSuccess, readOrCreateLocalMasterAdminApiKey, runtimeLaneMasterAdminApiKeySecretName, runtimeLaneOpenFgaSecretName, runtimeLanePostgresSecretName, runtimeLanePrimaryDbName, runtimeLaneSecretFieldManager, shellQuote } from "./remote";
import { V02_MASTER_ADMIN_API_KEY_SECRET, V02_MASTER_ADMIN_API_KEY_SECRET_KEY, V02_OPENFGA_AUTHN_SECRET_KEY, V02_OPENFGA_DATASTORE_URI_SECRET_KEY, V02_OPENFGA_DB_NAME, V02_OPENFGA_DB_USER, V02_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY, V02_OPENFGA_SECRET, V02_RUNTIME_NAMESPACE } from "./types";
import { keyValueLinesFromText, numericField } from "./v02-status";
import { compactCommandResult, legacyRuntimeK3s, legacyRuntimeK3sInlineScriptWithInput, isCommandSuccess, readOrCreateLocalMasterAdminApiKey, runtimeLaneMasterAdminApiKeySecretName, runtimeLaneOpenFgaSecretName, runtimeLanePostgresSecretName, runtimeLanePrimaryDbName, runtimeLaneSecretFieldManager, shellQuote } from "./remote";
import { DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET, DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET_KEY, DEFAULT_RUNTIME_LANE_OPENFGA_AUTHN_SECRET_KEY, DEFAULT_RUNTIME_LANE_OPENFGA_DATASTORE_URI_SECRET_KEY, DEFAULT_RUNTIME_LANE_OPENFGA_DB_NAME, DEFAULT_RUNTIME_LANE_OPENFGA_DB_USER, DEFAULT_RUNTIME_LANE_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY, DEFAULT_RUNTIME_LANE_OPENFGA_SECRET, DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE } from "./types";
import { keyValueLinesFromText, numericField } from "./runtime-status";
export function v02SecretScript(options: G14SecretOptions): string {
export function defaultRuntimeLaneSecretScript(options: LegacyRuntimeSecretOptions): string {
const spec = hwlabRuntimeLaneSpec(options.lane);
if (options.preset === "generic-delete") return v02DeleteSecretScript(options);
if (options.preset === "master-server-admin-api-key") return v02MasterAdminApiKeySecretScript(options, spec);
if (options.preset === "generic-delete") return defaultRuntimeLaneDeleteSecretScript(options);
if (options.preset === "master-server-admin-api-key") return defaultRuntimeLaneMasterAdminApiKeySecretScript(options, spec);
return [
v02OpenFgaSecretScript(options, spec),
defaultRuntimeLaneOpenFgaSecretScript(options, spec),
].join("\n");
}
export function v02DeleteSecretScript(options: G14SecretOptions): string {
export function defaultRuntimeLaneDeleteSecretScript(options: LegacyRuntimeSecretOptions): string {
return [
"set +e",
`namespace=${shellQuote(V02_RUNTIME_NAMESPACE)}`,
`namespace=${shellQuote(DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE)}`,
`name=${shellQuote(options.name)}`,
`dry_run=${shellQuote(options.dryRun ? "true" : "false")}`,
"preset=generic-delete",
@@ -62,7 +62,7 @@ export function v02DeleteSecretScript(options: G14SecretOptions): string {
].join("\n");
}
export function v02OpenFgaSecretScript(options: G14SecretOptions, spec: HwlabRuntimeLaneSpec): string {
export function defaultRuntimeLaneOpenFgaSecretScript(options: LegacyRuntimeSecretOptions, spec: HwlabRuntimeLaneSpec): string {
const selectedKey = options.key ?? "";
const namespace = spec.runtimeNamespace;
const openFgaSecret = runtimeLaneOpenFgaSecretName(spec);
@@ -70,8 +70,8 @@ export function v02OpenFgaSecretScript(options: G14SecretOptions, spec: HwlabRun
const primaryDbName = runtimeLanePrimaryDbName(spec);
const dedicatedDb = spec.lane === "v02";
const dbMode = dedicatedDb ? "dedicated" : "primary";
const dbName = dedicatedDb ? V02_OPENFGA_DB_NAME : primaryDbName;
const dbUser = dedicatedDb ? V02_OPENFGA_DB_USER : primaryDbName;
const dbName = dedicatedDb ? DEFAULT_RUNTIME_LANE_OPENFGA_DB_NAME : primaryDbName;
const dbUser = dedicatedDb ? DEFAULT_RUNTIME_LANE_OPENFGA_DB_USER : primaryDbName;
const dbHost = `${postgresSecret}.${namespace}.svc.cluster.local`;
const fieldManager = runtimeLaneSecretFieldManager(spec);
return [
@@ -79,9 +79,9 @@ export function v02OpenFgaSecretScript(options: G14SecretOptions, spec: HwlabRun
`namespace=${shellQuote(namespace)}`,
`name=${shellQuote(openFgaSecret)}`,
`selected_key=${shellQuote(selectedKey)}`,
`authn_key=${shellQuote(V02_OPENFGA_AUTHN_SECRET_KEY)}`,
`datastore_uri_key=${shellQuote(V02_OPENFGA_DATASTORE_URI_SECRET_KEY)}`,
`postgres_password_key=${shellQuote(V02_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY)}`,
`authn_key=${shellQuote(DEFAULT_RUNTIME_LANE_OPENFGA_AUTHN_SECRET_KEY)}`,
`datastore_uri_key=${shellQuote(DEFAULT_RUNTIME_LANE_OPENFGA_DATASTORE_URI_SECRET_KEY)}`,
`postgres_password_key=${shellQuote(DEFAULT_RUNTIME_LANE_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY)}`,
`db_mode=${shellQuote(dbMode)}`,
`db_name=${shellQuote(dbName)}`,
`db_user=${shellQuote(dbUser)}`,
@@ -248,7 +248,7 @@ export function v02OpenFgaSecretScript(options: G14SecretOptions, spec: HwlabRun
].join("\n");
}
export function v02MasterAdminApiKeySecretScript(options: G14SecretOptions, spec: HwlabRuntimeLaneSpec): string {
export function defaultRuntimeLaneMasterAdminApiKeySecretScript(options: LegacyRuntimeSecretOptions, spec: HwlabRuntimeLaneSpec): string {
const namespace = spec.runtimeNamespace;
const name = runtimeLaneMasterAdminApiKeySecretName(spec);
const fieldManager = runtimeLaneSecretFieldManager(spec);
@@ -256,7 +256,7 @@ export function v02MasterAdminApiKeySecretScript(options: G14SecretOptions, spec
"set +e",
`namespace=${shellQuote(namespace)}`,
`name=${shellQuote(name)}`,
`api_key_name=${shellQuote(V02_MASTER_ADMIN_API_KEY_SECRET_KEY)}`,
`api_key_name=${shellQuote(DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET_KEY)}`,
`action_request=${shellQuote(options.action)}`,
`dry_run=${shellQuote(options.dryRun ? "true" : "false")}`,
`field_manager=${shellQuote(fieldManager)}`,
@@ -314,13 +314,13 @@ export function v02MasterAdminApiKeySecretScript(options: G14SecretOptions, spec
].join("\n");
}
export function v02SecretStatusFromText(text: string, commandOk: boolean, exitCode: number | null, stderr: string): Record<string, unknown> {
export function defaultRuntimeLaneSecretStatusFromText(text: string, commandOk: boolean, exitCode: number | null, stderr: string): Record<string, unknown> {
const fields = keyValueLinesFromText(text);
if (fields.preset === "generic-delete") {
const absent = fields.afterExists !== "yes";
return {
ok: commandOk && absent,
namespace: fields.namespace || V02_RUNTIME_NAMESPACE,
namespace: fields.namespace || DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE,
secret: fields.secret || null,
preset: "generic-delete",
action: fields.action || null,
@@ -350,9 +350,9 @@ export function v02SecretStatusFromText(text: string, commandOk: boolean, exitCo
String(fields.afterApiKeyPrefix ?? "").startsWith("hwl_live_");
return {
ok: commandOk && healthy,
namespace: fields.namespace || V02_RUNTIME_NAMESPACE,
secret: fields.secret || V02_MASTER_ADMIN_API_KEY_SECRET,
key: fields.key || V02_MASTER_ADMIN_API_KEY_SECRET_KEY,
namespace: fields.namespace || DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE,
secret: fields.secret || DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET,
key: fields.key || DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET_KEY,
preset: "master-server-admin-api-key",
action: fields.action || null,
dryRun: fields.dryRun === "true",
@@ -370,8 +370,8 @@ export function v02SecretStatusFromText(text: string, commandOk: boolean, exitCo
stderr: commandOk ? "" : stderr.trim().slice(0, 2000),
valuesRedacted: true,
summary: healthy
? `${fields.secret || V02_MASTER_ADMIN_API_KEY_SECRET}/${fields.key || V02_MASTER_ADMIN_API_KEY_SECRET_KEY} exists`
: `${fields.secret || V02_MASTER_ADMIN_API_KEY_SECRET}/${fields.key || V02_MASTER_ADMIN_API_KEY_SECRET_KEY} missing`,
? `${fields.secret || DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET}/${fields.key || DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET_KEY} exists`
: `${fields.secret || DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET}/${fields.key || DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET_KEY} missing`,
};
}
if (fields.preset === "openfga") {
@@ -390,8 +390,8 @@ export function v02SecretStatusFromText(text: string, commandOk: boolean, exitCo
const healthy = keysHealthy && databaseHealthy;
return {
ok: commandOk && healthy,
namespace: fields.namespace || V02_RUNTIME_NAMESPACE,
secret: fields.secret || V02_OPENFGA_SECRET,
namespace: fields.namespace || DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE,
secret: fields.secret || DEFAULT_RUNTIME_LANE_OPENFGA_SECRET,
key: fields.key || null,
preset: "openfga",
action: fields.action || null,
@@ -422,21 +422,21 @@ export function v02SecretStatusFromText(text: string, commandOk: boolean, exitCo
},
postgresAdminSecretPresent: fields.postgresAdminSecretPresent === "yes",
postgresSecret: fields.postgresSecret || null,
dbName: fields.dbName || V02_OPENFGA_DB_NAME,
dbUser: fields.dbUser || V02_OPENFGA_DB_USER,
dbName: fields.dbName || DEFAULT_RUNTIME_LANE_OPENFGA_DB_NAME,
dbUser: fields.dbUser || DEFAULT_RUNTIME_LANE_OPENFGA_DB_USER,
applyExitCode: numericField(fields.applyExitCode),
dbEnsureExitCode: numericField(fields.dbEnsureExitCode),
exitCode,
stderr: commandOk ? "" : stderr.trim().slice(0, 2000),
valuesRedacted: true,
summary: healthy
? `${fields.secret || V02_OPENFGA_SECRET} keys and Postgres database exist`
: `${fields.secret || V02_OPENFGA_SECRET} keys or Postgres database missing`,
? `${fields.secret || DEFAULT_RUNTIME_LANE_OPENFGA_SECRET} keys and Postgres database exist`
: `${fields.secret || DEFAULT_RUNTIME_LANE_OPENFGA_SECRET} keys or Postgres database missing`,
};
}
return {
ok: false,
namespace: fields.namespace || V02_RUNTIME_NAMESPACE,
namespace: fields.namespace || DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE,
secret: fields.secret || null,
preset: fields.preset || null,
action: fields.action || null,
@@ -449,16 +449,16 @@ export function v02SecretStatusFromText(text: string, commandOk: boolean, exitCo
};
}
export function runG14Secret(options: G14SecretOptions): Record<string, unknown> {
export function runLegacyRuntimeSecret(options: LegacyRuntimeSecretOptions): Record<string, unknown> {
const spec = hwlabRuntimeLaneSpec(options.lane);
const script = v02SecretScript(options);
const script = defaultRuntimeLaneSecretScript(options);
const localAdminApiKey = options.preset === "master-server-admin-api-key"
? readOrCreateLocalMasterAdminApiKey(spec, options.action !== "ensure" || options.dryRun)
: null;
const result = options.preset === "master-server-admin-api-key"
? g14K3sInlineScriptWithInput(script, localAdminApiKey?.key ?? "", options.timeoutSeconds * 1000 + 2000)
: g14K3s(["sh", "--", script], options.timeoutSeconds * 1000);
const status = v02SecretStatusFromText(statusText(result), isCommandSuccess(result), result.exitCode, result.stderr);
? legacyRuntimeK3sInlineScriptWithInput(script, localAdminApiKey?.key ?? "", options.timeoutSeconds * 1000 + 2000)
: legacyRuntimeK3s(["sh", "--", script], options.timeoutSeconds * 1000);
const status = defaultRuntimeLaneSecretStatusFromText(statusText(result), isCommandSuccess(result), result.exitCode, result.stderr);
const dryRunOk = options.action === "ensure" && options.dryRun && isCommandSuccess(result);
const deleteDryRunOk = options.action === "delete" && options.dryRun && isCommandSuccess(result);
const ok = dryRunOk || deleteDryRunOk ? true : status.ok === true;
@@ -1,6 +1,6 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. source module for scripts/src/hwlab-g14.ts.
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. source module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-g14.ts:1205-1308 for #903.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:1205-1308 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
@@ -11,10 +11,10 @@ import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import type { CommandJsonResult } from "./types";
import { g14HostScript } from "./images";
import { legacyRuntimeHostScript } from "./images";
import { statusText } from "./pr-monitor";
import { commandJson, isCommandSuccess, nested, shellQuote, shortSha } from "./remote";
import { V02_GIT_URL, V02_LANE_SPEC } from "./types";
import { DEFAULT_RUNTIME_LANE_GIT_URL, DEFAULT_RUNTIME_LANE_LANE_SPEC } from "./types";
export function runtimeLaneCicdRepoEnsureScript(spec: HwlabRuntimeLaneSpec): string {
return [
@@ -59,14 +59,14 @@ export function runtimeLaneCicdRepoEnsureScript(spec: HwlabRuntimeLaneSpec): str
].join("\n");
}
export function v02CicdRepoEnsureScript(): string {
return runtimeLaneCicdRepoEnsureScript(V02_LANE_SPEC);
export function defaultRuntimeLaneCicdRepoEnsureScript(): string {
return runtimeLaneCicdRepoEnsureScript(DEFAULT_RUNTIME_LANE_LANE_SPEC);
}
export function resolveV02Head(): { sourceCommit: string | null; result: CommandJsonResult } {
const result = g14HostScript([
export function resolveDefaultRuntimeLaneHead(): { sourceCommit: string | null; result: CommandJsonResult } {
const result = legacyRuntimeHostScript([
"set -eu",
v02CicdRepoEnsureScript(),
defaultRuntimeLaneCicdRepoEnsureScript(),
"git --git-dir=\"$cicd_repo\" rev-parse refs/remotes/origin/v0.2",
].join("\n"), 180_000);
if (!isCommandSuccess(result)) return { sourceCommit: null, result };
@@ -75,12 +75,12 @@ export function resolveV02Head(): { sourceCommit: string | null; result: Command
return { sourceCommit: match?.[0] ?? null, result };
}
export function getV02Head(): string | null {
return resolveV02Head().sourceCommit;
export function getDefaultRuntimeLaneHead(): string | null {
return resolveDefaultRuntimeLaneHead().sourceCommit;
}
export function resolveV02LatestRemoteHead(): { sourceCommit: string | null; result: CommandJsonResult } {
const result = commandJson(["git", "ls-remote", V02_GIT_URL, "refs/heads/v0.2"], 30_000);
export function resolveDefaultRuntimeLaneLatestRemoteHead(): { sourceCommit: string | null; result: CommandJsonResult } {
const result = commandJson(["git", "ls-remote", DEFAULT_RUNTIME_LANE_GIT_URL, "refs/heads/v0.2"], 30_000);
if (!isCommandSuccess(result)) return { sourceCommit: null, result };
const output = statusText(result);
const match = /^[0-9a-f]{40}/imu.exec(output);
@@ -88,7 +88,7 @@ export function resolveV02LatestRemoteHead(): { sourceCommit: string | null; res
}
export function resolveRuntimeLaneHead(spec: HwlabRuntimeLaneSpec): { sourceCommit: string | null; result: CommandJsonResult } {
const result = g14HostScript([
const result = legacyRuntimeHostScript([
"set -eu",
runtimeLaneCicdRepoEnsureScript(spec),
`git --git-dir="$cicd_repo" rev-parse refs/remotes/origin/${spec.sourceBranch}`,
@@ -116,6 +116,6 @@ export function runtimeLaneRerunPipelineRunName(spec: HwlabRuntimeLaneSpec, sour
return `${runtimeLanePipelineRunName(spec, sourceCommit)}-r${stamp}`;
}
export function v02PipelineRunName(sourceCommit: string): string {
return runtimeLanePipelineRunName(V02_LANE_SPEC, sourceCommit);
export function defaultRuntimeLanePipelineRunName(sourceCommit: string): string {
return runtimeLanePipelineRunName(DEFAULT_RUNTIME_LANE_LANE_SPEC, sourceCommit);
}
+363
View File
@@ -0,0 +1,363 @@
// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. types module for scripts/src/hwlab-legacy-runtime.ts.
// Moved mechanically from scripts/src/hwlab-legacy-runtime.ts:1-288 for #903.
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { createHash, randomBytes } from "node:crypto";
import { repoRoot, rootPath, type Config } from "../config";
import { runCommand } from "../command";
import { cancelJob, listJobs, readJob, startJob } from "../jobs";
import { hwlabRequiredNoProxyEntries, hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec } from "../hwlab-node-lanes";
import { durationSeconds } from "./pr-monitor";
export const HWLAB_REPO = "pikasTech/HWLAB";
export const LEGACY_RUNTIME_SOURCE_BRANCH = "v0.3";
export const LEGACY_RUNTIME_PROVIDER = "NC01";
export const LEGACY_RUNTIME_WORKSPACE = "/root/hwlab-v03";
export const DEFAULT_RUNTIME_LANE_LANE_SPEC = hwlabRuntimeLaneSpec("v02") ?? hwlabRuntimeLaneSpec("v03");
export const DEFAULT_RUNTIME_LANE_SOURCE_BRANCH = DEFAULT_RUNTIME_LANE_LANE_SPEC.sourceBranch;
export const DEFAULT_RUNTIME_LANE_WORKSPACE = DEFAULT_RUNTIME_LANE_LANE_SPEC.workspace;
export const DEFAULT_RUNTIME_LANE_CICD_REPO = DEFAULT_RUNTIME_LANE_LANE_SPEC.cicdRepo;
export const DEV_NAMESPACE = "hwlab-dev";
export const PROD_NAMESPACE = "hwlab-prod";
export const CI_NAMESPACE = "hwlab-ci";
export const ARGO_NAMESPACE = "argocd";
export const DEV_APP = "hwlab-g14-dev";
export const PROD_APP = "hwlab-g14-prod";
export const DEFAULT_RUNTIME_LANE_APP = DEFAULT_RUNTIME_LANE_LANE_SPEC.app;
export const DEFAULT_RUNTIME_LANE_PIPELINE = DEFAULT_RUNTIME_LANE_LANE_SPEC.pipeline;
export const DEFAULT_RUNTIME_LANE_POLLER = "hwlab-v02-branch-poller";
export const DEFAULT_RUNTIME_LANE_RECONCILER = "hwlab-v02-control-plane-reconciler";
export const DEFAULT_RUNTIME_LANE_PIPELINERUN_PREFIX = DEFAULT_RUNTIME_LANE_LANE_SPEC.pipelineRunPrefix;
export const DEFAULT_RUNTIME_LANE_CONTROL_PLANE_FIELD_MANAGER = DEFAULT_RUNTIME_LANE_LANE_SPEC.controlPlaneFieldManager;
export const DEFAULT_RUNTIME_LANE_GIT_URL = DEFAULT_RUNTIME_LANE_LANE_SPEC.gitUrl;
export const DEFAULT_RUNTIME_LANE_GIT_READ_URL = DEFAULT_RUNTIME_LANE_LANE_SPEC.gitReadUrl;
export const DEFAULT_RUNTIME_LANE_GIT_WRITE_URL = DEFAULT_RUNTIME_LANE_LANE_SPEC.gitWriteUrl;
export const DEFAULT_RUNTIME_LANE_GITOPS_BRANCH = DEFAULT_RUNTIME_LANE_LANE_SPEC.gitopsBranch;
export const DEFAULT_RUNTIME_LANE_CATALOG_PATH = DEFAULT_RUNTIME_LANE_LANE_SPEC.catalogPath;
export const DEFAULT_RUNTIME_LANE_RUNTIME_PATH = DEFAULT_RUNTIME_LANE_LANE_SPEC.runtimePath;
export const DEFAULT_RUNTIME_LANE_RUNTIME_NAMESPACE = DEFAULT_RUNTIME_LANE_LANE_SPEC.runtimeNamespace;
export const DEFAULT_RUNTIME_LANE_OPENFGA_SECRET = "hwlab-v02-openfga";
export const DEFAULT_RUNTIME_LANE_OPENFGA_AUTHN_SECRET_KEY = "authn-preshared-key";
export const DEFAULT_RUNTIME_LANE_OPENFGA_DATASTORE_URI_SECRET_KEY = "datastore-uri";
export const DEFAULT_RUNTIME_LANE_OPENFGA_POSTGRES_PASSWORD_SECRET_KEY = "postgres-password";
export const DEFAULT_RUNTIME_LANE_OPENFGA_DB_NAME = "hwlab_openfga";
export const DEFAULT_RUNTIME_LANE_OPENFGA_DB_USER = "hwlab_openfga";
export const DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET = "hwlab-v02-master-server-admin-api-key";
export const DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_SECRET_KEY = "api-key";
export const DEFAULT_RUNTIME_LANE_MASTER_ADMIN_API_KEY_LOCAL_ENV = "/root/.config/hwlab-v02/master-server-admin-api-key.env";
export const DEFAULT_RUNTIME_LANE_REGISTRY_PREFIX = DEFAULT_RUNTIME_LANE_LANE_SPEC.registryPrefix;
export const DEFAULT_RUNTIME_LANE_BASE_IMAGE = DEFAULT_RUNTIME_LANE_LANE_SPEC.baseImage;
export const GIT_MIRROR_NAMESPACE = "devops-infra";
export const GIT_MIRROR_MANIFEST_FIELD_MANAGER = "unidesk-hwlab-git-mirror";
export const GIT_MIRROR_SYNC_JOB_PREFIX = "git-mirror-hwlab-sync-manual";
export const GIT_MIRROR_LEGACY_CRONJOB = "git-mirror-hwlab-sync";
export const LEGACY_RUNTIME_OBSERVABILITY_NAMESPACE = "devops-infra";
export const LEGACY_RUNTIME_OBSERVABILITY_FIELD_MANAGER = "unidesk-g14-observability";
export const LEGACY_RUNTIME_PROMETHEUS_OPERATOR_VERSION = "v0.91.0";
export const LEGACY_RUNTIME_PROMETHEUS_VERSION = "v3.12.0";
export const LEGACY_RUNTIME_PROMETHEUS_NAME = "g14-shared";
export const LEGACY_RUNTIME_PROMETHEUS_SERVICE = "prometheus-g14-shared";
export const LEGACY_RUNTIME_PROMETHEUS_SERVICE_ACCOUNT = "g14-observability-prometheus";
export const LEGACY_RUNTIME_PROMETHEUS_OPERATOR_RELEASE_ASSET = `https://github.com/prometheus-operator/prometheus-operator/releases/download/${LEGACY_RUNTIME_PROMETHEUS_OPERATOR_VERSION}/bundle.yaml`;
export const DEFAULT_RUNTIME_LANE_SERVICE_IDS = [...DEFAULT_RUNTIME_LANE_LANE_SPEC.serviceIds];
export const DEFAULT_RUNTIME_LANE_CLOUD_WEB_URL = DEFAULT_RUNTIME_LANE_LANE_SPEC.publicWebUrl;
export const DEFAULT_RUNTIME_LANE_CLOUD_API_URL = DEFAULT_RUNTIME_LANE_LANE_SPEC.publicApiUrl;
export const DEFAULT_RUNTIME_LANE_OBSERVABILITY_SERVICE_IDS = [
"hwlab-agent-skills",
"hwlab-cloud-api",
"hwlab-cloud-web",
"hwlab-deepseek-proxy",
"hwlab-edge-proxy",
];
export const DEFAULT_RUNTIME_LANE_OBSERVABILITY_EXPECTED_TARGET_COUNT = DEFAULT_RUNTIME_LANE_OBSERVABILITY_SERVICE_IDS.length;
export const DEFAULT_RUNTIME_LANE_OBSERVABILITY_QUERIES = {
scrapeReachable: 'up{namespace="hwlab-v02"}',
sidecarServing: 'hwlab_service_up{namespace="hwlab-v02"}',
businessHealthProbe: 'hwlab_service_health_probe_success{namespace="hwlab-v02"}',
healthProbeConfigured: 'hwlab_service_health_probe_configured{namespace="hwlab-v02"}',
healthProbeStatusCode: 'hwlab_service_health_probe_status_code{namespace="hwlab-v02"}',
healthProbeDurationSeconds: 'hwlab_service_health_probe_duration_seconds{namespace="hwlab-v02"}',
processUptimeSeconds: 'hwlab_service_process_uptime_seconds{namespace="hwlab-v02"}',
scrapeDurationSeconds: 'scrape_duration_seconds{namespace="hwlab-v02"}',
scrapeSamplesScraped: 'scrape_samples_scraped{namespace="hwlab-v02"}',
};
export const DEFAULT_RUNTIME_LANE_OBSERVABILITY_CLOSEOUT_QUERY_NAMES = ["scrapeReachable", "sidecarServing", "businessHealthProbe"] as const;
export const DEFAULT_RUNTIME_LANE_OBSERVABILITY_BOOLEAN_QUERY_NAMES = [...DEFAULT_RUNTIME_LANE_OBSERVABILITY_CLOSEOUT_QUERY_NAMES, "healthProbeConfigured"];
export function defaultRuntimeLanePipelineServiceIds(): string[] {
return [...DEFAULT_RUNTIME_LANE_SERVICE_IDS];
}
export const LEGACY_RUNTIME_CI_TOOLS_IMAGE_REPO = "127.0.0.1:5000/hwlab/hwlab-ci-node-tools";
export const LEGACY_RUNTIME_CI_TOOLS_BASE_TAG = "node22-alpine-v1";
export const DEFAULT_INTERVAL_SECONDS = 600;
export const DEFAULT_MAX_CYCLES = 0;
export const DEFAULT_TIMEOUT_SECONDS = 1800;
export const LEGACY_RUNTIME_BRIEF_INDEX_ISSUE = 7;
export const BEIJING_OFFSET_MS = 8 * 60 * 60 * 1000;
export const DEFAULT_RUNTIME_LANE_BUILD_TASKRUN_WARNING_SECONDS = 120;
export const DEFAULT_RUNTIME_LANE_BUILD_TASKRUN_CRITICAL_SECONDS = 180;
export const DEFAULT_RUNTIME_LANE_CONTROL_PLANE_REFRESH_TTL_MS = 5 * 60 * 1000;
export const DEFAULT_RUNTIME_LANE_CONTROL_PLANE_REFRESH_LOCK_STALE_MS = 5 * 60 * 1000;
export const DEFAULT_RUNTIME_LANE_GIT_MIRROR_PRESYNC_MAX_WAIT_MS = 120 * 1000;
export const DEFAULT_RUNTIME_LANE_GIT_MIRROR_PRESYNC_LOCK_STALE_MS = 5 * 60 * 1000;
export const DEFAULT_RUNTIME_LANE_GIT_MIRROR_PRESYNC_POLL_MS = 3 * 1000;
export type LegacyRuntimeMonitorLane = "g14" | HwlabRuntimeLane;
export interface LegacyRuntimeMonitorOptions {
lane: LegacyRuntimeMonitorLane;
intervalSeconds: number;
maxCycles: number;
once: boolean;
dryRun: boolean;
worker: boolean;
timeoutSeconds: number;
}
export interface LegacyRuntimeRecordRolloutOptions {
prNumber: number;
sourceCommit?: string;
pipelineRun?: string;
gitopsRevision?: string;
mergedAt?: string;
pipelineSucceededAt?: string;
finishedAt?: string;
dryRun: boolean;
}
export interface LegacyRuntimeControlPlaneOptions {
action: "status" | "closeout" | "apply" | "trigger-current" | "refresh" | "cleanup-runs" | "cleanup-released-pvs" | "runtime-migration";
lane: HwlabRuntimeLane | "g14" | "all";
dryRun: boolean;
confirm: boolean;
wait: boolean;
rerun: boolean;
allowLiveDbRead: boolean;
timeoutSeconds: number;
minAgeMinutes: number;
limit: number;
sourceCommit?: string;
pipelineRun?: string;
history: boolean;
}
export interface LegacyRuntimeLegacyRetirementOptions {
action: "status" | "plan" | "execute";
dryRun: boolean;
confirm: boolean;
wait: boolean;
timeoutSeconds: number;
reason: string;
}
export type DefaultRuntimeLaneStatusTargetMode = "latest-source-head" | "source-commit" | "pipeline-run";
export interface DefaultRuntimeLaneControlPlaneStatusTarget {
sourceCommit?: string | null;
pipelineRun?: string | null;
mode?: DefaultRuntimeLaneStatusTargetMode;
includeHistory?: boolean;
}
export interface LegacyRuntimeToolsImageOptions {
action: "status" | "build";
name: "ci-node-tools";
tag: string;
dockerfile: string;
dryRun: boolean;
confirm: boolean;
timeoutSeconds: number;
}
export interface LegacyRuntimeUpstreamImageOptions {
action: "status" | "ensure";
name: "openfga";
tag: string;
dryRun: boolean;
confirm: boolean;
timeoutSeconds: number;
}
export interface LegacyRuntimeGitMirrorOptions {
action: "status" | "apply" | "sync" | "flush";
lane: HwlabRuntimeLane;
dryRun: boolean;
confirm: boolean;
wait: boolean;
timeoutSeconds: number;
}
export interface LegacyRuntimeObservabilityOptions {
action: "status" | "apply" | "query" | "targets" | "boundary" | "closeout";
lane: "v02";
dryRun: boolean;
confirm: boolean;
wait: boolean;
timeoutSeconds: number;
query: string;
expectCount?: number;
expectValue?: string;
}
export interface LegacyRuntimeSecretOptions {
action: "status" | "ensure" | "delete";
lane: HwlabRuntimeLane;
dryRun: boolean;
confirm: boolean;
name: string;
key?: string;
preset: "openfga" | "master-server-admin-api-key" | "generic-delete";
timeoutSeconds: number;
}
export interface CommandJsonResult {
ok: boolean;
command: string[];
exitCode: number | null;
stdout: string;
stderr: string;
parsed: unknown | null;
durationMs?: number;
timedOut?: boolean;
stdoutBytes?: number;
stderrBytes?: number;
}
export interface RemoteAsyncCommandSpec {
script: string;
timeoutSeconds: number;
label: string;
token: string;
command: string[];
}
export interface ShellSection {
stdout: string;
exitCode: number | null;
}
export interface OpenPullRequest {
number: number;
title?: string;
url?: string;
baseRefName?: string;
headRefName?: string;
mergeable?: string | null;
mergeStateStatus?: string | null;
draft?: boolean;
}
export interface CiServiceMetric {
taskRun: string;
serviceId: string;
status: string;
durationSeconds: number | null;
buildBackend: string | null;
reusedFrom: string | null;
imageTag: string | null;
sourceCommitId: string | null;
}
export interface CiPipelineMetrics {
ok: boolean;
pipelineRun: string;
serviceTotal: number;
reusedCount: number;
rebuildCount: number;
reusedServices: string[];
rebuildServices: string[];
services: CiServiceMetric[];
degradedReason?: string;
rawText?: string;
}
export interface DefaultRuntimeLanePrCommentInput {
pr: OpenPullRequest;
phase: string;
state: "waiting-ci" | "blocked" | "merge-failed" | "cd-started" | "cd-succeeded" | "cd-superseded" | "cd-failed" | "cd-timeout" | "cd-blocked";
startedAt: string;
observedAt: string;
elapsedSeconds: number | null;
preflight?: Record<string, unknown>;
merge?: Record<string, unknown>;
sourceCommit?: string | null;
pipelineRun?: string | null;
cd?: Record<string, unknown>;
flush?: Record<string, unknown>;
dryRun?: boolean;
message?: string;
}
+2 -2
View File
@@ -1,10 +1,10 @@
// SPEC: PJ2026-01060505 Workbench Performance draft-2026-06-17-p0.
// Responsibility: HWLAB node transport/delegation adapters for legacy shared commands.
import type { Config } from "./config";
import { runHwlabG14Command } from "./hwlab-g14";
import { runHwlabLegacyRuntimeCommand } from "./hwlab-legacy-runtime";
export type DelegatedNodeDomain = "control-plane" | "git-mirror";
export async function runDelegatedHwlabNodeCommand(config: Config, domain: DelegatedNodeDomain, args: string[]): Promise<unknown> {
return runHwlabG14Command(config, [domain, ...args]);
return runHwlabLegacyRuntimeCommand(config, [domain, ...args]);
}
+4 -4
View File
@@ -319,7 +319,7 @@ export function renderJobLaunchSummary(command: string, result: unknown): Render
function summarizeJobProgress(job: JobRecord, maxBytes = 96_000, tails?: { stdoutTail: string; stderrTail: string }): JobProgressSummary {
const nowMs = Date.now();
const knownWorkflow = job.name === "hwlab_g14_v02_trigger_current";
const v02PrMonitorWorkflow = job.name === "hwlab_g14_v02_pr_monitor";
const defaultRuntimeLanePrMonitorWorkflow = job.name === "hwlab_g14_v02_pr_monitor";
const runtimeLaneTriggerWorkflow = /^hwlab_nodes_v[0-9]{2}_control-plane_trigger-current$/u.test(job.name);
const agentRunYamlLaneTriggerWorkflow = /^agentrun_v[0-9]{2}_trigger_current$/u.test(job.name);
const gitMirrorWorkflow = job.name === "hwlab_g14_git_mirror_sync"
@@ -332,12 +332,12 @@ function summarizeJobProgress(job: JobRecord, maxBytes = 96_000, tails?: { stdou
const progressTailBytes = Math.max(4096, Math.floor(maxBytes));
const stderrTail = tails?.stderrTail ?? tailFile(job.stderrFile, progressTailBytes);
const agentRunYamlLaneProgressObserved = agentRunYamlLaneTriggerWorkflow || hasAgentRunYamlLaneProgressEvents(stderrTail);
if (!knownWorkflow && !v02PrMonitorWorkflow && !runtimeLaneTriggerWorkflow && !agentRunYamlLaneProgressObserved && !gitMirrorWorkflow) return genericJobProgress(job, stderrTail);
if (!knownWorkflow && !defaultRuntimeLanePrMonitorWorkflow && !runtimeLaneTriggerWorkflow && !agentRunYamlLaneProgressObserved && !gitMirrorWorkflow) return genericJobProgress(job, stderrTail);
const stdoutTail = tails?.stdoutTail ?? tailFile(job.stdoutFile, progressTailBytes);
if (gitMirrorWorkflow) return summarizeGitMirrorJobProgress(job, stdoutTail, stderrTail, nowMs);
if (runtimeLaneTriggerWorkflow) return summarizeRuntimeLaneTriggerJobProgress(job, stdoutTail, stderrTail, nowMs);
if (agentRunYamlLaneProgressObserved) return summarizeAgentRunYamlLaneTriggerJobProgress(job, stdoutTail, stderrTail, nowMs);
const events = v02PrMonitorWorkflow
const events = defaultRuntimeLanePrMonitorWorkflow
? [
...parseJsonLineEvents(stderrTail, "hwlab.v02.pr-monitor.progress"),
...parseJsonLineEvents(stderrTail, "hwlab.v02.trigger.progress"),
@@ -354,7 +354,7 @@ function summarizeJobProgress(job: JobRecord, maxBytes = 96_000, tails?: { stdou
? false
: null;
const lastEventAt = stringField(lastEvent.at);
const kind = events.length > 0 || knownWorkflow || v02PrMonitorWorkflow ? "hwlab-v02-trigger" : "generic";
const kind = events.length > 0 || knownWorkflow || defaultRuntimeLanePrMonitorWorkflow ? "hwlab-v02-trigger" : "generic";
const elapsedSeconds = jobElapsedSeconds(job, nowMs);
const stageElapsedSeconds = currentStageElapsedSeconds(events, stage, stageStatus, job, nowMs);
const lastEventAgeSeconds = lastEventAt === null ? null : secondsSince(lastEventAt, job.finishedAt ?? nowMs);
@@ -1,9 +1,9 @@
export const d601NativeKubeconfig = "/etc/rancher/k3s/k3s.yaml";
export const d601RequiredNodeName = "d601";
export const defaultNativeKubeconfig = "/etc/rancher/k3s/k3s.yaml";
export const requiredNativeNodeName = "d601";
export type D601K3sGuardStatus = "pass" | "refused" | "blocked";
export type K3sGuardStatus = "pass" | "refused" | "blocked";
export interface D601K3sTargetObservation {
export interface K3sTargetObservation {
kubeconfig: string;
expectedKubeconfig?: string;
currentContext: string | null;
@@ -13,7 +13,7 @@ export interface D601K3sTargetObservation {
combinedText: string;
}
export interface D601K3sDefaultKubectlDiagnostic {
export interface K3sDefaultKubectlDiagnostic {
checked: boolean;
currentContext: string | null;
apiServer: string | null;
@@ -22,8 +22,8 @@ export interface D601K3sDefaultKubectlDiagnostic {
summary: string;
}
export interface D601K3sGuardClassification {
status: D601K3sGuardStatus;
export interface K3sGuardClassification {
status: K3sGuardStatus;
refusal: boolean;
refusalSignals: string[];
kubeconfig: string;
@@ -35,11 +35,11 @@ export interface D601K3sGuardClassification {
requiredNodeName: string;
requiredNodePresent: boolean;
commandsOk: boolean;
defaultKubectlDiagnostic?: D601K3sDefaultKubectlDiagnostic;
defaultKubectlDiagnostic?: K3sDefaultKubectlDiagnostic;
summary: string;
}
export interface D601K3sGuardShellOptions {
export interface K3sGuardShellOptions {
passOutput?: "stdout" | "stderr" | "quiet";
}
@@ -47,7 +47,7 @@ function uniqueSignals(signals: Array<string | null>): string[] {
return [...new Set(signals.filter((signal): signal is string => signal !== null))];
}
export function d601ForbiddenKubeSignals(text: string): string[] {
export function forbiddenKubeSignals(text: string): string[] {
return uniqueSignals([
/docker-desktop/iu.test(text) ? "docker-desktop" : null,
/desktop-control-plane/iu.test(text) ? "desktop-control-plane" : null,
@@ -55,13 +55,13 @@ export function d601ForbiddenKubeSignals(text: string): string[] {
]);
}
export function classifyD601DefaultKubectlDiagnostic(input: {
export function classifyDefaultKubectlDiagnostic(input: {
currentContext: string | null;
apiServer: string | null;
combinedText: string;
commandsOk: boolean;
}): D601K3sDefaultKubectlDiagnostic {
const refusalSignals = d601ForbiddenKubeSignals(input.combinedText);
}): K3sDefaultKubectlDiagnostic {
const refusalSignals = forbiddenKubeSignals(input.combinedText);
if (!input.commandsOk) {
return {
checked: true,
@@ -69,7 +69,7 @@ export function classifyD601DefaultKubectlDiagnostic(input: {
apiServer: input.apiServer,
refusalSignals,
status: "unavailable",
summary: "Default kubectl diagnostic could not read context/server; this does not block the explicit D601 target.",
summary: "Default kubectl diagnostic could not read context/server; this does not block the explicit target.",
};
}
if (refusalSignals.length > 0) {
@@ -79,7 +79,7 @@ export function classifyD601DefaultKubectlDiagnostic(input: {
apiServer: input.apiServer,
refusalSignals,
status: "stale-forbidden-default",
summary: "Default kubectl resolves to a forbidden local control-plane signal; explicit D601 KUBECONFIG remains the deploy target.",
summary: "Default kubectl resolves to a forbidden local control-plane signal; explicit KUBECONFIG remains the deploy target.",
};
}
return {
@@ -92,32 +92,32 @@ export function classifyD601DefaultKubectlDiagnostic(input: {
};
}
export function classifyD601K3sTarget(
observation: D601K3sTargetObservation,
defaultKubectlDiagnostic?: D601K3sDefaultKubectlDiagnostic,
): D601K3sGuardClassification {
const expectedKubeconfig = observation.expectedKubeconfig ?? d601NativeKubeconfig;
const refusalSignals = d601ForbiddenKubeSignals(observation.combinedText);
const requiredNodePresent = observation.nodeNames.includes(d601RequiredNodeName);
export function classifyK3sTarget(
observation: K3sTargetObservation,
defaultKubectlDiagnostic?: K3sDefaultKubectlDiagnostic,
): K3sGuardClassification {
const expectedKubeconfig = observation.expectedKubeconfig ?? defaultNativeKubeconfig;
const refusalSignals = forbiddenKubeSignals(observation.combinedText);
const requiredNodePresent = observation.nodeNames.includes(requiredNativeNodeName);
const wrongKubeconfig = observation.kubeconfig !== expectedKubeconfig;
const refusal = refusalSignals.length > 0;
const status: D601K3sGuardStatus = refusal
const status: K3sGuardStatus = refusal
? "refused"
: !observation.commandsOk || wrongKubeconfig || !requiredNodePresent
? "blocked"
: "pass";
const defaultStale = defaultKubectlDiagnostic?.status === "stale-forbidden-default";
const summary = refusal
? "Refusing D601 k3s operation because the explicit target kubeconfig resolved to a forbidden Docker Desktop control-plane signal."
? "Refusing k3s operation because the explicit target kubeconfig resolved to a forbidden Docker Desktop control-plane signal."
: wrongKubeconfig
? `D601 k3s guard blocked: expected explicit KUBECONFIG=${expectedKubeconfig}.`
? `k3s target guard blocked: expected explicit KUBECONFIG=${expectedKubeconfig}.`
: !observation.commandsOk
? "D601 k3s guard blocked: explicit target kubeconfig could not read context, server, and nodes."
? "k3s target guard blocked: explicit target kubeconfig could not read context, server, and nodes."
: !requiredNodePresent
? `D601 k3s guard blocked: explicit target kubeconfig did not report node ${d601RequiredNodeName}.`
? `k3s target guard blocked: explicit target kubeconfig did not report node ${requiredNativeNodeName}.`
: defaultStale
? "D601 native k3s guard passed with explicit KUBECONFIG; stale default kubectl context was observed only as a diagnostic."
: "D601 native k3s guard passed with explicit KUBECONFIG.";
? "native k3s target guard passed with explicit KUBECONFIG; stale default kubectl context was observed only as a diagnostic."
: "native k3s target guard passed with explicit KUBECONFIG.";
return {
status,
refusal,
@@ -128,7 +128,7 @@ export function classifyD601K3sTarget(
apiServer: observation.apiServer,
nodeNames: observation.nodeNames,
nodeCount: observation.nodeNames.length,
requiredNodeName: d601RequiredNodeName,
requiredNodeName: requiredNativeNodeName,
requiredNodePresent,
commandsOk: observation.commandsOk,
...(defaultKubectlDiagnostic === undefined ? {} : { defaultKubectlDiagnostic }),
@@ -140,49 +140,49 @@ function shellQuote(value: string): string {
return `'${value.replace(/'/gu, "'\\''")}'`;
}
export function d601K3sGuardShellLines(kubeconfig = d601NativeKubeconfig, options: D601K3sGuardShellOptions = {}): string[] {
export function k3sGuardShellLines(kubeconfig = defaultNativeKubeconfig, options: K3sGuardShellOptions = {}): string[] {
const passOutput = options.passOutput ?? "stdout";
const passLine = passOutput === "quiet"
? ":"
: passOutput === "stderr"
? "printf 'd601_native_k3s_guard=pass kubeconfig=%s context=%s server=%s node=%s\\n' \"$required_kubeconfig\" \"$context\" \"$server\" \"$required_node\" >&2"
: "printf 'd601_native_k3s_guard=pass kubeconfig=%s context=%s server=%s node=%s\\n' \"$required_kubeconfig\" \"$context\" \"$server\" \"$required_node\"";
? "printf 'k3s_target_guard=pass kubeconfig=%s context=%s server=%s node=%s\\n' \"$required_kubeconfig\" \"$context\" \"$server\" \"$required_node\" >&2"
: "printf 'k3s_target_guard=pass kubeconfig=%s context=%s server=%s node=%s\\n' \"$required_kubeconfig\" \"$context\" \"$server\" \"$required_node\"";
return [
`export KUBECONFIG=${shellQuote(kubeconfig)}`,
"d601_k3s_guard() {",
"k3s_target_guard() {",
` required_kubeconfig=${shellQuote(kubeconfig)}`,
` required_node=${shellQuote(d601RequiredNodeName)}`,
` required_node=${shellQuote(requiredNativeNodeName)}`,
" if [ \"${KUBECONFIG:-}\" != \"$required_kubeconfig\" ]; then",
" printf 'd601_native_k3s_guard=blocked reason=wrong-kubeconfig expected=%s actual=%s\\n' \"$required_kubeconfig\" \"${KUBECONFIG:-<unset>}\" >&2",
" printf 'k3s_target_guard=blocked reason=wrong-kubeconfig expected=%s actual=%s\\n' \"$required_kubeconfig\" \"${KUBECONFIG:-<unset>}\" >&2",
" return 1",
" fi",
" if ! command -v kubectl >/dev/null 2>&1; then",
" echo 'd601_native_k3s_guard=blocked reason=kubectl-missing' >&2",
" echo 'k3s_target_guard=blocked reason=kubectl-missing' >&2",
" return 1",
" fi",
" if ! context=$(kubectl config current-context 2>&1); then",
" printf 'd601_native_k3s_guard=blocked reason=context-read-failed detail=%s\\n' \"$context\" >&2",
" printf 'k3s_target_guard=blocked reason=context-read-failed detail=%s\\n' \"$context\" >&2",
" return 1",
" fi",
" if ! server=$(kubectl config view --minify -o 'jsonpath={.clusters[0].cluster.server}' 2>&1); then",
" printf 'd601_native_k3s_guard=blocked reason=server-read-failed detail=%s\\n' \"$server\" >&2",
" printf 'k3s_target_guard=blocked reason=server-read-failed detail=%s\\n' \"$server\" >&2",
" return 1",
" fi",
" if ! nodes=$(kubectl get nodes -o 'jsonpath={range .items[*]}{.metadata.name}{\"\\n\"}{end}' 2>&1); then",
" printf 'd601_native_k3s_guard=blocked reason=nodes-read-failed detail=%s\\n' \"$nodes\" >&2",
" printf 'k3s_target_guard=blocked reason=nodes-read-failed detail=%s\\n' \"$nodes\" >&2",
" return 1",
" fi",
" combined=$(printf '%s\\n%s\\n%s\\n' \"$context\" \"$server\" \"$nodes\")",
" if printf '%s\\n' \"$combined\" | grep -Eiq 'docker-desktop|desktop-control-plane|127\\.0\\.0\\.1:11700'; then",
" printf 'd601_native_k3s_guard=refused reason=forbidden-control-plane context=%s server=%s nodes=%s\\n' \"$context\" \"$server\" \"$(printf '%s' \"$nodes\" | tr '\\n' ',')\" >&2",
" printf 'k3s_target_guard=refused reason=forbidden-control-plane context=%s server=%s nodes=%s\\n' \"$context\" \"$server\" \"$(printf '%s' \"$nodes\" | tr '\\n' ',')\" >&2",
" return 1",
" fi",
" if ! printf '%s\\n' \"$nodes\" | grep -Fx \"$required_node\" >/dev/null; then",
" printf 'd601_native_k3s_guard=blocked reason=missing-d601-node context=%s server=%s nodes=%s\\n' \"$context\" \"$server\" \"$(printf '%s' \"$nodes\" | tr '\\n' ',')\" >&2",
" printf 'k3s_target_guard=blocked reason=missing-required-node context=%s server=%s nodes=%s\\n' \"$context\" \"$server\" \"$(printf '%s' \"$nodes\" | tr '\\n' ',')\" >&2",
" return 1",
" fi",
` ${passLine}`,
"}",
"d601_k3s_guard",
"k3s_target_guard",
];
}
+2 -2
View File
@@ -283,7 +283,7 @@ const forbiddenAutomaticActions = [
"git reset --hard live worktree",
];
export function runD601RecoveryGuardrails(config: UniDeskConfig, fixture: RecoveryGuardrailsFixture = {}): RecoveryGuardrailsResult {
export function runRecoveryGuardrails(config: UniDeskConfig, fixture: RecoveryGuardrailsFixture = {}): RecoveryGuardrailsResult {
const observedAt = fixture.observedAt ?? new Date().toISOString();
const filesRead: string[] = [];
const commandsAttempted: string[][] = [];
@@ -352,7 +352,7 @@ export function runD601RecoveryGuardrails(config: UniDeskConfig, fixture: Recove
};
}
export function compactD601RecoveryGuardrails(result: RecoveryGuardrailsResult): CompactRecoveryGuardrailsResult {
export function compactRecoveryGuardrails(result: RecoveryGuardrailsResult): CompactRecoveryGuardrailsResult {
return {
ok: result.ok,
surface: result.surface,