// SPEC: PJ2026-01060307 控制面模块化 draft-2026-06-25-p0. control-plane module for scripts/src/agentrun.ts. // Moved mechanically from scripts/src/agentrun.ts:2022-2592 for #903. // SPEC: PJ2026-01020108 cancel lifecycle + PJ2026-01020205 AipodSpec binding + PJ2026-01020302 session policy + PJ2026-01020305 cancel control + PJ2026-01060305/06 YAML execution policy and bounded output draft-2026-06-25-p0. // Exposes AgentRun lane-scoped policy, AipodSpec SecretRef binding, cancel lifecycle, and bounded default output in the UniDesk CLI. import { chmodSync, copyFileSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { spawnSync } from "node:child_process"; import { rootPath, type UniDeskConfig } from "../config"; import type { RenderedCliResult } from "../output"; import { applyLocalCaddyManagedSite } from "../pk01-caddy"; import { runSshCommandCapture, type SshCaptureResult } from "../ssh"; import { runRemoteSshCommandCapture } from "../remote"; import { startJob } from "../jobs"; import { AGENTRUN_CONFIG_PATH, agentRunLaneSummary, agentRunPipelineRunName, agentRunProviderCredentialRefs, resolveAgentRunLaneTarget, type AgentRunCancelLifecycleSpec, type AgentRunLaneSpec, } from "../agentrun-lanes"; import { agentRunImageArtifact, placeholderAgentRunImage, renderedFilesDigest, renderedObjectsDigest, renderAgentRunControlPlaneManifests, renderAgentRunGitopsFiles, type AgentRunArtifactService, } from "../agentrun-manifests"; import { sha256Fingerprint } from "../platform-infra-ops-library"; import type { CleanupReleasedPvOptions, CleanupRunnersOptions, CleanupRunsOptions, ConfirmOptions, GitMirrorOptions, LaneConfirmOptions, RefreshOptions, SecretSyncOptions, StatusOptions } from "./options"; import { agentRunControlPlaneStatusCommand } from "./public-exposure"; import { applyYamlScript, manifestObjectRef, yamlLaneGitMirrorStatusScript } from "./secrets"; import { compactAgentRunLaneStatusTarget, compactLaneSecretsStatus } from "./trigger"; import { capture, captureJsonPayload, compactCapture, record, stringOrNull, timedStatusStage } from "./utils"; import { yamlLaneRuntimeStatusScript, yamlLaneSourceStatusScript } from "./yaml-lane"; export function parseSecretSyncOptions(args: string[]): SecretSyncOptions { const base = parseConfirmOptions(args); let node: string | null = null; let lane: string | null = null; for (let index = 0; index < args.length; index += 1) { const arg = args[index]; if (arg === "--confirm" || arg === "--dry-run") continue; if (arg === "--node") { const value = args[index + 1]; if (value === undefined || value.startsWith("--")) throw new Error("--node requires a value"); node = value; index += 1; continue; } if (arg === "--lane") { const value = args[index + 1]; if (value === undefined || value.startsWith("--")) throw new Error("--lane requires a value"); lane = value; index += 1; continue; } throw new Error(`unsupported secret-sync option: ${arg}`); } return { ...base, node, lane }; } export function parseLaneConfirmOptions(args: string[]): LaneConfirmOptions { const base = parseConfirmOptions(args); let node: string | null = null; let lane: string | null = null; for (let index = 0; index < args.length; index += 1) { const arg = args[index]; if (arg === "--confirm" || arg === "--dry-run") continue; if (arg === "--node") { const value = args[index + 1]; if (value === undefined || value.startsWith("--")) throw new Error("--node requires a value"); node = value; index += 1; continue; } if (arg === "--lane") { const value = args[index + 1]; if (value === undefined || value.startsWith("--")) throw new Error("--lane requires a value"); lane = value; index += 1; continue; } throw new Error(`unsupported control-plane option: ${arg}`); } if (node === null && lane === null) throw new Error("control-plane apply requires --node and --lane"); return { ...base, node, lane }; } export function parseRefreshOptions(args: string[]): RefreshOptions { const base = parseConfirmOptions(args); let node: string | null = null; let lane: string | null = null; let full = false; let raw = false; for (let index = 0; index < args.length; index += 1) { const arg = args[index]; if (arg === "--confirm" || arg === "--dry-run") continue; if (arg === "--full") { full = true; continue; } if (arg === "--raw") { raw = true; full = true; continue; } if (arg === "--node") { const value = args[index + 1]; if (value === undefined || value.startsWith("--")) throw new Error("--node requires a value"); node = value; index += 1; continue; } if (arg === "--lane") { const value = args[index + 1]; if (value === undefined || value.startsWith("--")) throw new Error("--lane requires a value"); lane = value; index += 1; continue; } throw new Error(`unsupported refresh option: ${arg}`); } return { ...base, node, lane, full, raw }; } export function parseConfirmOptions(args: string[]): ConfirmOptions { if (args.includes("--confirm") && args.includes("--dry-run")) throw new Error("accepts only one of --confirm or --dry-run"); return { confirm: args.includes("--confirm"), dryRun: args.includes("--dry-run") || !args.includes("--confirm"), }; } export function parseGitMirrorOptions(args: string[]): GitMirrorOptions { validateOptions(args, new Set(["--confirm", "--dry-run", "--wait"]), new Set(["--timeout-seconds", "--node", "--lane"])); const base = parseConfirmOptions(args); const timeoutIndex = args.indexOf("--timeout-seconds"); const timeoutSeconds = timeoutIndex >= 0 ? Number(args[timeoutIndex + 1]) : 300; if (!Number.isFinite(timeoutSeconds) || timeoutSeconds < 30) throw new Error("--timeout-seconds must be a number >= 30"); return { ...base, node: optionValue(args, "--node") ?? null, lane: optionValue(args, "--lane") ?? null, timeoutSeconds, wait: args.includes("--wait"), }; } export function parseCleanupRunnersOptions(args: string[]): CleanupRunnersOptions { validateOptions(args, new Set(["--confirm", "--dry-run", "--force-active", "--full", "--raw"]), new Set(["--timeout-seconds", "--node", "--lane"])); const base = parseConfirmOptions(args); const raw = args.includes("--raw"); return { ...base, node: optionValue(args, "--node") ?? null, lane: optionValue(args, "--lane") ?? null, timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", 180, 600), forceActive: args.includes("--force-active"), full: raw || args.includes("--full"), raw, }; } export function parseCleanupRunsOptions(args: string[]): CleanupRunsOptions { validateOptions(args, new Set(["--confirm", "--dry-run"]), new Set(["--min-age-minutes", "--limit", "--timeout-seconds", "--node", "--lane"])); const base = parseConfirmOptions(args); return { ...base, node: optionValue(args, "--node") ?? null, lane: optionValue(args, "--lane") ?? null, minAgeMinutes: positiveIntegerOption(args, "--min-age-minutes", 60, 10080), limit: positiveIntegerOption(args, "--limit", 20, 500), timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", 180, 600), }; } export function parseCleanupReleasedPvOptions(args: string[]): CleanupReleasedPvOptions { validateOptions(args, new Set(["--confirm", "--dry-run"]), new Set(["--limit", "--timeout-seconds", "--node", "--lane"])); const base = parseConfirmOptions(args); return { ...base, node: optionValue(args, "--node") ?? null, lane: optionValue(args, "--lane") ?? null, limit: positiveIntegerOption(args, "--limit", 20, 500), timeoutSeconds: positiveIntegerOption(args, "--timeout-seconds", 120, 600), }; } export function validateOptions(args: string[], booleanOptions: Set, valueOptions: Set): void { for (let index = 0; index < args.length; index += 1) { const arg = args[index]; if (booleanOptions.has(arg)) continue; if (valueOptions.has(arg)) { const value = args[index + 1]; if (value === undefined || value.startsWith("--")) throw new Error(`${arg} requires a value`); index += 1; continue; } throw new Error(`unsupported option: ${arg}`); } } export function optionValue(args: string[], name: string): string | undefined { const index = args.indexOf(name); if (index === -1) return undefined; const value = args[index + 1]; if (value === undefined || value.startsWith("--")) throw new Error(`${name} requires a value`); return value; } export function positiveIntegerOption(args: string[], name: string, defaultValue: number, maxValue: number): number { const raw = optionValue(args, name); if (raw === undefined) return defaultValue; const value = Number(raw); if (!Number.isInteger(value) || value < 0) throw new Error(`${name} must be a non-negative integer`); return Math.min(value, maxValue); } export async function controlPlanePlan(_config: UniDeskConfig, options: StatusOptions): Promise> { const target = resolveAgentRunLaneTarget(options); const spec = target.spec; return { ok: true, command: "agentrun control-plane plan", mode: "yaml-declared-node-lane", configPath: target.configPath, target: agentRunLaneSummary(spec), plannedChecks: [ "source-branch-exists", "source-worktree-exists-and-clean", "git-mirror-services-ready", "ci-namespace-pipeline-serviceaccount", "argo-application-alignment", "runtime-namespace-manager-service", "database-secretref-present", "local-postgres-absent-when-external", ], deploymentBoundary: { mutation: false, note: "plan/status are read-only. Long writes must use controlled AgentRun/Platform DB commands and this YAML target.", }, next: { status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`, postgresStatus: spec.database.configRef ? `bun scripts/cli.ts platform-db postgres status --config ${spec.database.configRef}` : null, postgresApply: spec.database.configRef ? `bun scripts/cli.ts platform-db postgres apply --config ${spec.database.configRef} --confirm` : null, controlPlaneApply: `bun scripts/cli.ts agentrun control-plane apply --node ${spec.nodeId} --lane ${spec.lane} --dry-run`, }, valuesPrinted: false, }; } export async function controlPlaneApply(config: UniDeskConfig, options: LaneConfirmOptions): Promise> { const { configPath, spec } = resolveAgentRunLaneTarget(options); const objects = renderAgentRunControlPlaneManifests(spec); const manifestYaml = `${objects.map((object) => Bun.YAML.stringify(object).trim()).join("\n---\n")}\n`; const objectRefs = objects.map((object) => manifestObjectRef(object)); const plan = { node: spec.nodeId, kubeRoute: spec.nodeKubeRoute, lane: spec.lane, version: spec.version, objectCount: objects.length, objects: objectRefs, manifestBytes: Buffer.byteLength(manifestYaml, "utf8"), manifestDigest: renderedObjectsDigest(objects), fieldManager: "unidesk-agentrun-control-plane", valuesPrinted: false, }; if (options.dryRun || !options.confirm) { return { ok: true, command: "agentrun control-plane apply", mode: "dry-run", mutation: false, configPath, target: agentRunLaneSummary(spec), plan, next: { confirm: `bun scripts/cli.ts agentrun control-plane apply --node ${spec.nodeId} --lane ${spec.lane} --confirm`, status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`, }, valuesPrinted: false, }; } const applied = await capture(config, spec.nodeKubeRoute, ["sh", "--", applyYamlScript(manifestYaml, "unidesk-agentrun-control-plane", false)]); const payload = captureJsonPayload(applied); return { ok: applied.exitCode === 0 && payload.ok !== false, command: "agentrun control-plane apply", mode: "confirmed-apply", mutation: true, configPath, target: agentRunLaneSummary(spec), plan, result: payload, capture: compactCapture(applied, { full: applied.exitCode !== 0, stdoutTailChars: 5000, stderrTailChars: 5000 }), next: { secretSync: `bun scripts/cli.ts agentrun control-plane secret-sync --node ${spec.nodeId} --lane ${spec.lane} --confirm`, status: `bun scripts/cli.ts agentrun control-plane status --node ${spec.nodeId} --lane ${spec.lane}`, }, valuesPrinted: false, }; } export async function status(config: UniDeskConfig, options: StatusOptions): Promise> { return await statusYamlLane(config, options, resolveAgentRunLaneTarget(options)); } export async function statusYamlLane(config: UniDeskConfig, options: StatusOptions, target: { configPath: string; spec: AgentRunLaneSpec }): Promise> { const spec = target.spec; const sourceProbe = await timedStatusStage("source", () => capture(config, `${spec.nodeRoute}:${spec.source.workspace}`, ["sh", "--", yamlLaneSourceStatusScript(spec)])); const sourcePayload = captureJsonPayload(sourceProbe.value); const branchTipCommit = stringOrNull(sourcePayload.remoteBranchCommit) ?? stringOrNull(sourcePayload.localHead); const initialSourceCommit = options.sourceCommit ?? stringOrNull(sourcePayload.remoteBranchCommit) ?? stringOrNull(sourcePayload.localHead); const pipelineRunName = options.pipelineRun ?? (initialSourceCommit ? agentRunPipelineRunName(spec, initialSourceCommit) : null); const [runtimeProbe, mirrorProbe] = await Promise.all([ timedStatusStage("runtime", () => capture(config, spec.nodeKubeRoute, ["sh", "--", yamlLaneRuntimeStatusScript(spec, pipelineRunName)])), timedStatusStage("git-mirror", () => capture(config, spec.nodeKubeRoute, ["sh", "--", yamlLaneGitMirrorStatusScript(spec)])), ]); const runtimePayload = captureJsonPayload(runtimeProbe.value); const mirrorPayload = captureJsonPayload(mirrorProbe.value); const pipeline = record(runtimePayload.pipeline); const pipelineRunStatus = record(runtimePayload.pipelineRun); const argo = record(runtimePayload.argo); const manager = record(runtimePayload.manager); const database = record(runtimePayload.database); const secrets = record(runtimePayload.secrets); const localPostgres = record(runtimePayload.localPostgres); const expectedGitopsRevision = stringOrNull(mirrorPayload.gitopsCommit); const mirrorSourceCommit = stringOrNull(mirrorPayload.sourceCommit); const pipelineRunSourceCommit = stringOrNull(pipelineRunStatus.sourceCommit); const sourceCommit = options.sourceCommit ?? (options.pipelineRun !== null ? pipelineRunSourceCommit ?? initialSourceCommit : initialSourceCommit); const managerSourceMatchesExpected = Boolean(sourceCommit && manager.sourceCommit === sourceCommit); const argoSyncedToGitops = Boolean(expectedGitopsRevision && argo.revision === expectedGitopsRevision); const pipelineSucceeded = pipelineRunStatus.status === "True"; const sourceWorktreeDetached = sourcePayload.branch === "HEAD"; const targetMode = options.pipelineRun !== null ? "pipeline-run" : options.sourceCommit !== null ? "source-commit" : "latest-source-head"; const sourceBranchAdvanced = targetMode !== "latest-source-head" && sourceCommit !== null && branchTipCommit !== null && sourceCommit !== branchTipCommit; const branchDrift = { targetMode, sourceBranch: spec.source.branch, currentBranchTipCommit: branchTipCommit, targetSourceCommit: sourceCommit, pipelineRunSourceCommit, sourceBranchAdvanced, targetSupersededByCurrentBranch: sourceBranchAdvanced, closeoutHint: sourceBranchAdvanced ? "target PipelineRun/source commit is not the current branch tip; verify the branch tip contains the fix, then trigger-current again for latest-tip closeout" : null, valuesPrinted: false, }; const warnings = [ ...(sourceWorktreeDetached ? ["source-worktree-detached"] : []), ...(sourceBranchAdvanced ? ["source-branch-advanced-after-target"] : []), ]; const blockers = [ ...(sourcePayload.workspaceExists === true ? [] : ["source-worktree-missing"]), ...(sourcePayload.remoteBranchExists === true ? [] : ["source-branch-missing"]), ...(sourcePayload.workspaceClean === true || sourcePayload.workspaceExists !== true ? [] : ["source-worktree-dirty"]), ...(mirrorPayload.readReady === true ? [] : ["git-mirror-read-not-ready"]), ...(mirrorPayload.writeReady === true ? [] : ["git-mirror-write-not-ready"]), ...(mirrorPayload.cacheReady === true ? [] : ["git-mirror-cache-not-ready"]), ...(runtimePayload.ciNamespaceExists === true ? [] : ["ci-namespace-missing"]), ...(pipeline.exists === true ? [] : ["pipeline-missing"]), ...(pipelineRunName !== null && pipelineRunStatus.exists !== true ? ["pipelinerun-missing"] : []), ...(pipelineRunStatus.exists !== true || pipelineRunStatus.status === undefined || pipelineRunStatus.status === null || pipelineRunStatus.status === "True" ? [] : ["pipelinerun-not-succeeded"]), ...(runtimePayload.serviceAccountExists === true ? [] : ["ci-serviceaccount-missing"]), ...(argo.exists === true ? [] : ["argo-application-missing"]), ...(mirrorPayload.readReady === true && expectedGitopsRevision === null ? ["gitops-revision-unresolved"] : []), ...(argo.exists !== true || expectedGitopsRevision === null || argoSyncedToGitops ? [] : ["argo-revision-stale"]), ...(runtimePayload.runtimeNamespaceExists === true ? [] : ["runtime-namespace-missing"]), ...(manager.deploymentExists === true ? [] : ["manager-deployment-missing"]), ...(manager.deploymentExists !== true || sourceCommit === null || managerSourceMatchesExpected ? [] : ["manager-source-stale"]), ...(manager.serviceExists === true ? [] : ["manager-service-missing"]), ...(spec.database.mode === "external-postgres" && database.secretPresent !== true ? ["database-secret-missing"] : []), ...(secrets.ready === true ? [] : ["lane-secret-missing"]), ...(spec.database.localPostgresExpectedAbsent && localPostgres.absent !== true ? ["local-postgres-present"] : []), ]; const ciBlockerNames = new Set(["ci-namespace-missing", "pipeline-missing", "pipelinerun-missing", "pipelinerun-not-succeeded", "ci-serviceaccount-missing"]); const ciBlockers = blockers.filter((blocker) => ciBlockerNames.has(blocker)); const runtimeBlockers = blockers.filter((blocker) => !ciBlockerNames.has(blocker)); const aligned = blockers.length === 0 && pipelineSucceeded && argoSyncedToGitops && managerSourceMatchesExpected; const runtimeAligned = runtimeBlockers.length === 0 && argoSyncedToGitops && managerSourceMatchesExpected; const ciEvidenceMissing = pipelineRunName !== null && pipelineRunStatus.exists !== true; const mirrorAlreadySynced = mirrorPayload.readReady === true && mirrorPayload.writeReady === true && sourceCommit !== null && mirrorSourceCommit === sourceCommit && expectedGitopsRevision !== null; const runtimeAlignment = { pipelineSucceeded, argoRevision: stringOrNull(argo.revision), argoSyncedToGitops, managerSourceCommit: stringOrNull(manager.sourceCommit), managerSourceMatchesExpected, runtimeAligned, }; const statusFullCommand = agentRunControlPlaneStatusCommand(spec, options, true); const triggerCurrentCommand = `bun scripts/cli.ts agentrun control-plane trigger-current --node ${spec.nodeId} --lane ${spec.lane} --confirm`; const nextAction = aligned ? { code: "none", summary: "lane aligned; no action required", command: null } : ciEvidenceMissing && runtimeAligned && mirrorAlreadySynced ? { code: "ci-evidence-missing", summary: "runtime and git mirror are aligned, but the expected PipelineRun evidence is missing; rerun trigger-current to recreate CI evidence", command: triggerCurrentCommand } : sourceBranchAdvanced ? { code: "target-superseded", summary: "target commit is no longer the branch tip; trigger-current against latest source before closeout", command: triggerCurrentCommand } : ciBlockers.length > 0 ? { code: "ci-blocked", summary: `CI evidence is blocked: ${ciBlockers.join(", ")}`, command: statusFullCommand } : runtimeBlockers.length > 0 ? { code: "runtime-blocked", summary: `runtime alignment is blocked: ${runtimeBlockers.join(", ")}`, command: statusFullCommand } : { code: "inspect-full-status", summary: "alignment is inconclusive; inspect full status details", command: statusFullCommand }; const compactSourceStatus = { workspaceExists: sourcePayload.workspaceExists ?? false, workspaceClean: sourcePayload.workspaceClean ?? null, branch: sourcePayload.branch ?? null, remoteBranchExists: sourcePayload.remoteBranchExists ?? false, remoteBranchCommit: sourcePayload.remoteBranchCommit ?? null, workspaceDetached: sourceWorktreeDetached, }; const compactGitMirrorStatus = { readReady: mirrorPayload.readReady ?? false, writeReady: mirrorPayload.writeReady ?? false, cacheMode: mirrorPayload.cacheMode ?? null, cacheReady: mirrorPayload.cacheReady ?? false, cachePvcExists: mirrorPayload.cachePvcExists ?? false, cacheHostPath: mirrorPayload.cacheHostPath ?? null, sourceCommit: mirrorSourceCommit, gitopsCommit: expectedGitopsRevision, alreadySynced: mirrorAlreadySynced, }; const compactCiStatus = { namespaceExists: runtimePayload.ciNamespaceExists ?? false, serviceAccountExists: runtimePayload.serviceAccountExists ?? false, pipelineExists: pipeline.exists ?? false, pipelineRun: { name: pipelineRunName, exists: pipelineRunStatus.exists ?? false, status: pipelineRunStatus.status ?? null, reason: pipelineRunStatus.reason ?? null, sourceCommit: pipelineRunSourceCommit, }, evidenceMissing: ciEvidenceMissing, blockers: ciBlockers, }; const compactArgoStatus = { exists: argo.exists ?? false, application: argo.application ?? null, revision: stringOrNull(argo.revision), syncStatus: argo.syncStatus ?? null, healthStatus: argo.healthStatus ?? null, syncedToGitops: argoSyncedToGitops, }; const compactRuntimeStatus = { namespaceExists: runtimePayload.runtimeNamespaceExists ?? false, manager: { deploymentExists: manager.deploymentExists ?? false, serviceExists: manager.serviceExists ?? false, sourceCommit: stringOrNull(manager.sourceCommit), sourceMatchesExpected: managerSourceMatchesExpected, }, databaseSecretPresent: database.secretPresent ?? null, secretsReady: secrets.ready ?? null, localPostgresAbsent: localPostgres.absent ?? null, blockers: runtimeBlockers, }; const detailedSummary = { aligned, runtimeAligned, ciEvidenceMissing, mirrorAlreadySynced, blockers, warnings, sourceCommit, expectedPipelineRun: pipelineRunName, expectedGitopsRevision, runtimeAlignment, branchDrift, source: { workspaceExists: sourcePayload.workspaceExists ?? false, workspaceClean: sourcePayload.workspaceClean ?? null, branch: sourcePayload.branch ?? null, workspaceDetached: sourceWorktreeDetached, localHead: sourcePayload.localHead ?? null, remoteBranchExists: sourcePayload.remoteBranchExists ?? false, remoteBranchCommit: sourcePayload.remoteBranchCommit ?? null, }, gitMirror: { readReady: mirrorPayload.readReady ?? false, writeReady: mirrorPayload.writeReady ?? false, cacheMode: mirrorPayload.cacheMode ?? null, cacheReady: mirrorPayload.cacheReady ?? false, cachePvcExists: mirrorPayload.cachePvcExists ?? false, cacheHostPath: mirrorPayload.cacheHostPath ?? null, repositoryCount: Array.isArray(mirrorPayload.repositories) ? mirrorPayload.repositories.length : null, sourceCommit: mirrorSourceCommit, gitopsCommit: expectedGitopsRevision, alreadySynced: mirrorAlreadySynced, }, ci: { namespaceExists: runtimePayload.ciNamespaceExists ?? false, serviceAccountExists: runtimePayload.serviceAccountExists ?? false, pipeline, pipelineRun: pipelineRunStatus, evidenceMissing: ciEvidenceMissing, blockers: ciBlockers, }, argo, runtime: { namespaceExists: runtimePayload.runtimeNamespaceExists ?? false, manager: { deploymentExists: manager.deploymentExists ?? false, serviceExists: manager.serviceExists ?? false, deployment: manager.deployment ?? null, service: manager.service ?? null, image: manager.image ?? null, sourceCommit: stringOrNull(manager.sourceCommit), sourceMatchesExpected: managerSourceMatchesExpected, }, database, secrets: compactLaneSecretsStatus(secrets), localPostgres, }, nextAction, }; const commanderSummary = { aligned, runtimeAligned, ciEvidenceMissing, mirrorAlreadySynced, blockers, warnings, sourceCommit, expectedPipelineRun: pipelineRunName, expectedGitopsRevision, runtimeAlignment, branchDrift, source: compactSourceStatus, gitMirror: compactGitMirrorStatus, ci: compactCiStatus, argo: compactArgoStatus, runtime: compactRuntimeStatus, nextAction, }; const result: Record = { ok: sourceProbe.value.exitCode === 0 && runtimeProbe.value.exitCode === 0 && mirrorProbe.value.exitCode === 0 && blockers.length === 0, command: "agentrun control-plane status", mode: "yaml-declared-node-lane", configPath: target.configPath, target: options.full || options.raw ? agentRunLaneSummary(spec) : compactAgentRunLaneStatusTarget(spec), summary: options.full || options.raw ? detailedSummary : commanderSummary, alignment: { aligned, runtimeAligned, ciEvidenceMissing, mirrorAlreadySynced, blockers, warnings, sourceCommit, expectedPipelineRun: pipelineRunName, expectedGitopsRevision, runtimeAlignment, branchDrift, }, timings: { sourceMs: sourceProbe.elapsedMs, runtimeMs: runtimeProbe.elapsedMs, gitMirrorMs: mirrorProbe.elapsedMs, totalMs: sourceProbe.elapsedMs + Math.max(runtimeProbe.elapsedMs, mirrorProbe.elapsedMs), }, disclosure: { output: options.full || options.raw ? "full" : "compact-summary", full: options.full, raw: options.raw, omittedWhenCompact: options.full || options.raw ? [] : ["full target YAML summary", "detailed source/runtime/gitMirror objects", "capture stdout/stderr tails for successful probes"], fullCommand: statusFullCommand, }, next: { action: nextAction, plan: `bun scripts/cli.ts agentrun control-plane plan --node ${spec.nodeId} --lane ${spec.lane}`, postgresStatus: spec.database.configRef ? `bun scripts/cli.ts platform-db postgres status --config ${spec.database.configRef}` : null, postgresApply: spec.database.configRef ? `bun scripts/cli.ts platform-db postgres apply --config ${spec.database.configRef} --confirm` : null, secretSync: `bun scripts/cli.ts agentrun control-plane secret-sync --node ${spec.nodeId} --lane ${spec.lane} --confirm`, restart: `bun scripts/cli.ts agentrun control-plane restart --node ${spec.nodeId} --lane ${spec.lane} --confirm`, statusFull: statusFullCommand, triggerCurrent: ciEvidenceMissing || sourceBranchAdvanced ? triggerCurrentCommand : null, triggerLatest: sourceBranchAdvanced ? `bun scripts/cli.ts agentrun control-plane trigger-current --node ${spec.nodeId} --lane ${spec.lane} --confirm` : null, }, valuesPrinted: false, }; if (options.full || options.raw || sourceProbe.value.exitCode !== 0 || runtimeProbe.value.exitCode !== 0 || mirrorProbe.value.exitCode !== 0) { result.captures = { source: compactCapture(sourceProbe.value, { full: options.full || options.raw || sourceProbe.value.exitCode !== 0 }), runtime: compactCapture(runtimeProbe.value, { full: options.full || options.raw || runtimeProbe.value.exitCode !== 0 }), gitMirror: compactCapture(mirrorProbe.value, { full: options.full || options.raw || mirrorProbe.value.exitCode !== 0 }), }; } if (options.full || options.raw) { result.source = sourcePayload; result.runtime = runtimePayload; result.gitMirror = mirrorPayload; } return result; }