import { randomBytes } from "node:crypto"; import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import type { UniDeskConfig } from "./config"; import { rootPath } from "./config"; import type { RenderedCliResult } from "./output"; import { capture, compactCapture, createYamlFieldReader, parseJsonOutput, readYamlRecord, shQuote, sha256Fingerprint, } from "./platform-infra-ops-library"; import { materializeYamlComposition } from "./yaml-composition"; import { kubernetesWatchOneEventShellFunction } from "./kubernetes-watch"; import { pacNodeReadOnlyNext, pacReadOnlyNext, resolveCicdDeliveryAuthority } from "./cicd-delivery-authority"; import { DEFAULT_READ_ONLY_LONG_POLL_TIMEOUT_MS, parseStrictDuration, readOnlyLongPollTransportTimeoutMs, runReadOnlyLongPoll } from "./read-only-long-poll"; import { canonicalizePacPipelineSpec, pacSourceArtifactSafeError, pacValueEvidence, parsePacSourceArtifactOptions, renderPacSourceArtifactResult, runPacSourceArtifact, type PacSourceArtifactBinding, type PacSourceArtifactRuntimeObservation, type PacSourceArtifactSpec, } from "./platform-infra-pipelines-as-code-source-artifact"; import { pacAdmissionDesiredIdentity } from "./platform-infra-pac-provenance"; import { pacConsumerRbacDesiredIdentity, renderPacConsumerRbacDesiredFragment } from "./platform-infra-pac-consumer-rbac"; import { runGiteaMirrorBootstrapPreflight, runPlatformInfraGiteaCommand } from "./platform-infra-gitea"; import { readGiteaConfig } from "./platform-infra-gitea-config"; import { pacBootstrapYamlMatchFailure, projectPacBootstrapResult, renderPacBootstrap, resolvePacBootstrapMirrorRepository, } from "./platform-infra-pipelines-as-code-bootstrap"; import { featureConfigSchemaHistoryFields, renderFeatureConfigSchemaLine } from "./platform-infra-pac-feature-config-projection"; import { observePacDeliveryTiming, observePacHistoryDeliveryBudget, observePacStatusDeliveryBudget, type PacDeliveryTimingPolicy, } from "./platform-infra-pac-delivery-timing"; const configFile = rootPath("config", "platform-infra", "pipelines-as-code.yaml"); const configLabel = "config/platform-infra/pipelines-as-code.yaml"; const remoteScriptFile = rootPath("scripts", "src", "platform-infra-pipelines-as-code-remote.sh"); const evaluatorFile = rootPath("scripts", "native", "cicd", "pac-status-evaluator.cjs"); const sourceArtifactRuntimeObserverFile = rootPath("scripts", "native", "cicd", "pac-source-artifact-runtime.mjs"); const fieldManager = "unidesk-platform-infra-pipelines-as-code"; const y = createYamlFieldReader(configLabel); function kubernetesLabelValue(value: string): string { const normalized = value .trim() .replace(/[^A-Za-z0-9_.-]+/gu, "-") .replace(/^-+|-+$/gu, "") .slice(0, 63); return normalized.length > 0 ? normalized : "unspecified"; } interface PacTarget { id: string; route: string; namespace: string; role: string; enabled: boolean; } export interface PacConfig { version: number; kind: "platform-infra-pipelines-as-code"; metadata: { id: string; owner: string; spec: string; relatedIssues: number[]; }; defaults: { targetId: string; consumerId: string; }; display: { timeZone: string; }; deliveryTiming: PacDeliveryTimingPolicy; observability: { configRef: string; tracesEndpoint: string; serviceName: string; propagation: string[]; samplingMode: "parentbased_traceidratio"; samplingRatio: number; exporterFailure: { warning: true; blocking: false; timeoutMs: number }; }; release: { version: string; manifestUrl: string; namespace: string; controllerServiceName: string; controllerServicePort: number; waitTimeoutSeconds: number; }; deliveryProvenance: { version: string; markerAnnotation: string; controllerIdentity: string; policyName: string; bindingName: string; }; capabilities: { sentinelInternalPublish: { enabled: boolean; admissionProvenance: "unavailable"; trackingIssue: string; }; }; closeout: { gitOpsMirrorFlush: { enabled: boolean; waitTimeoutSeconds: number; maxAttempts: number; }; }; targets: PacTarget[]; gitea: { configRef: string; internalBaseUrl: string; admin: { sourceRoot: string; sourceRef: string; format: "line-pair"; usernameLine: number; passwordLine: number; apiUsername: string; }; webhook: { secretRoot: string; secretSourceRef: string; events: string[]; branch: string; }; }; repositories: PacRepository[]; consumers: PacConsumer[]; validationWarnings: PacConfigValidationWarning[]; } export interface PacConfigValidationWarning { readonly code: | "pac-unselected-consumer-parse-invalid" | "pac-unselected-consumer-invalid" | "pac-unselected-repository-parse-invalid" | "pac-unselected-repository-invalid"; readonly object: { readonly kind: "consumer" | "repository"; readonly id: string }; readonly consumer: string | null; readonly configPath: string; readonly blocking: false; readonly evidence: string; } interface PacRepository { id: string; name: string; namespace: string; providerType: "gitea"; url: string; cloneUrl: string; owner: string; repo: string; secretName: string; tokenKey: string; webhookSecretKey: string; concurrencyLimit: number; params: Record; } interface PacConsumer { id: string; node: string; lane: string; namespace: string; pipeline: string; pipelineRunPrefix: string; argoNamespace: string; argoApplication: string; argoBootstrap: { project: string; repoUrl: string; targetRevision: string; path: string; destinationNamespace: string; automated: boolean; repositoryCredential: { secretName: string; username: string; } | null; } | null; deliveryProvenance: { required: true; markerValue: string; executionServiceAccountName: string; gitOps: { repoUrl: string; targetRevision: string }; } | null; repositoryRef: string; params: Record; closeoutGitOpsMirrorFlush: boolean; closeoutGitOpsMirrorLane: "v02" | "v03" | null; sourceArtifact: PacSourceArtifactSpec | null; runnerServiceAccount: { name: string; automountServiceAccountToken: false; roleBindingName: string; } | null; } interface CommonOptions { targetId: string | null; consumerId: string | null; full: boolean; raw: boolean; json: boolean; } export interface PipelinesAsCodeNodeStatusOptions { targetId: string | null; nodeId: string | null; full: boolean; raw: boolean; } interface HistoryOptions extends CommonOptions { limit: number; detailId: string | null; } export interface PacHistoryConsumerIdentity { readonly id: string; readonly node: string; readonly pipelineRunPrefix: string; } export interface PacHistoryConsumerSelection { readonly selectedConsumerIds: readonly string[]; readonly nextConsumerId: string | null; } interface CloseoutOptions extends CommonOptions { sourceCommit: string | null; wait: boolean; timeoutMs: number; } interface ApplyOptions extends CommonOptions { confirm: boolean; dryRun: boolean; wait: boolean; } interface SecretMaterial { adminUsername: string; adminPassword: string; adminFingerprint: string; webhookSecret: string; webhookFingerprint: string; webhookPath: string; } export async function runPlatformInfraPipelinesAsCodeCommand(config: UniDeskConfig, args: string[]): Promise | RenderedCliResult> { const [action = "plan"] = args; if (action === "help" || action === "--help") return help(args[1] ?? null); if (action === "plan") { const options = parseCommonOptions(args.slice(1)); const result = plan(options); return options.full || options.raw ? result : options.json ? compactPlanJson(result) : renderPlan(result); } if (action === "apply") { const options = parseApplyOptions(args.slice(1)); const result = await apply(config, options); return options.json || options.full || options.raw ? result : renderApply(result); } if (action === "bootstrap") { const options = parseApplyOptions(args.slice(1)); const result = await bootstrap(config, options); return options.full || options.raw ? result : options.json ? projectPacBootstrapResult(result) : renderPacBootstrap(result); } if (action === "status") { const options = parseCommonOptions(args.slice(1)); const result = await status(config, options); return options.full || options.raw ? result : options.json ? compactStatusJson(result) : renderStatus(result); } if (action === "closeout") { let options: CloseoutOptions; try { options = parseCloseoutOptions(args.slice(1)); } catch (error) { return { ok: false, action: "platform-infra-pipelines-as-code-closeout-validation", mutation: false, code: "validation-failed", message: error instanceof Error ? error.message : String(error), usage: "bun scripts/cli.ts platform-infra pipelines-as-code closeout --target --consumer --wait [--timeout 120s]", valuesPrinted: false, }; } const result = await closeout(config, options); return options.full || options.raw ? result : options.json ? compactCloseoutJson(result) : renderCloseout(result); } if (action === "history" || action === "runs") { const options = parseHistoryOptions(args.slice(1)); const result = await history(config, options); return options.raw ? result : options.full ? compactHistoryJson(result, true) : options.json ? compactHistoryJson(result) : renderHistory(result); } if (action === "delivery-timing" || action === "timing") { const options = parseCommonOptions(args.slice(1)); const result = await deliveryTiming(config, options); return options.full || options.raw || options.json ? result : renderDeliveryTiming(result); } if (action === "debug-step") { const options = parseHistoryOptions(args.slice(1)); const result = await debugStep(config, options); return options.full || options.raw ? result : options.json ? result : renderDebugStep(result); } if (action === "source-artifact") { if (args.length === 1 || args.slice(1).includes("--help") || args.slice(1).includes("-h")) return sourceArtifactHelp(); const structuredError = args.includes("--json") || args.includes("--full"); try { const options = parsePacSourceArtifactOptions(args.slice(1)); const pac = readPacConfig({ consumerId: options.consumerId }); const target = resolveTarget(pac, options.targetId); const consumer = resolveConsumer(pac, options.consumerId); if (consumer.node.toLowerCase() !== target.id.toLowerCase()) throw new Error(`Pipelines-as-Code consumer ${consumer.id} belongs to ${consumer.node}, not target ${target.id}`); if (consumer.sourceArtifact === null) throw new Error(`Pipelines-as-Code consumer ${consumer.id} has no sourceArtifact owner in ${configLabel}`); const repository = resolveRepository(pac, consumer.repositoryRef); const binding: PacSourceArtifactBinding = { target: { id: target.id }, consumer: { id: consumer.id, node: consumer.node, lane: consumer.lane, namespace: consumer.namespace, pipeline: consumer.pipeline, pipelineRunPrefix: consumer.pipelineRunPrefix, argoNamespace: consumer.argoNamespace, argoApplication: consumer.argoApplication, argoBootstrap: consumer.argoBootstrap, deliveryProvenance: consumer.deliveryProvenance === null ? null : { markerAnnotation: pac.deliveryProvenance.markerAnnotation, markerValue: consumer.deliveryProvenance.markerValue, executionServiceAccountName: consumer.deliveryProvenance.executionServiceAccountName, }, sourceArtifact: consumer.sourceArtifact, }, repository: { id: repository.id, url: repository.url, cloneUrl: repository.cloneUrl, owner: repository.owner, repo: repository.repo, params: consumerParams(repository, consumer), }, }; const result = { ...await runPacSourceArtifact(binding, options, async ({ desiredSpec, desiredWrapper, provenance, sourceCommit }) => await observeSourceArtifactRuntime(config, target, consumer, desiredSpec, desiredWrapper, provenance, sourceCommit)), warnings: pac.validationWarnings, }; return options.json || options.full ? result : renderPacSourceArtifactResult(result); } catch (error) { const safeError = pacSourceArtifactSafeError(error); if (structuredError) { return { ok: false, action: "platform-infra-pipelines-as-code-source-artifact-validation", error: safeError, next: "Fix the declared owner, exact source identity, worktree proof, or CLI arguments; rerun plan before write.", }; } return sourceArtifactValidationError(safeError); } } return { ok: false, error: "unsupported-platform-infra-pipelines-as-code-command", args, help: help(null) }; } function sourceArtifactHelp(): RenderedCliResult { const lines = [ "PAC SOURCE ARTIFACT", "Generate and verify consumer-declared PaC source artifacts from owning YAML plus the shared domain renderer.", "", "Usage:", " ... source-artifact plan --target --consumer --source-worktree ", " ... source-artifact check --target --consumer --source-worktree ", " ... source-artifact write --target --consumer --source-worktree --confirm", " ... source-artifact status --target --consumer --source-worktree [--source-commit ]", " ... source-artifact verify-runtime --target --consumer --source-worktree --source-commit ", "", "plan/check/write compare desired YAML-rendered state with source files and never access runtime.", "status is non-gating runtime diagnostics; verify-runtime is fail-closed and binds the PipelineRun to the exact source commit.", "Use --json or --full for explicit structured output; source specs and Secret values are never printed.", ]; return { ok: true, command: "platform-infra-pipelines-as-code-source-artifact-help", renderedText: `${lines.join("\n")}\n`, contentType: "text/plain" }; } function sourceArtifactValidationError(error: ReturnType): RenderedCliResult { const details = Object.entries(error.details).map(([key, value]) => `${key}=${value}`).join(" "); return { ok: false, command: "platform-infra-pipelines-as-code-source-artifact-validation", renderedText: `PAC SOURCE ARTIFACT ERROR\ncode=${error.code} evidence=${error.evidence}${details ? ` details=${details}` : ""}\nnext=Fix the declared owner, exact source identity, worktree proof, or CLI arguments; rerun plan before write.\n`, contentType: "text/plain", }; } function help(scope: string | null): Record { if (scope === "platform-bootstrap") { return { command: "platform-infra pipelines-as-code help platform-bootstrap", scope: "explicit-initial-platform-bootstrap", configTruth: configLabel, usage: [ "bun scripts/cli.ts platform-infra pipelines-as-code bootstrap --target --consumer --dry-run", "bun scripts/cli.ts platform-infra pipelines-as-code bootstrap --target --consumer --confirm", "bun scripts/cli.ts platform-infra pipelines-as-code apply --target --dry-run", "bun scripts/cli.ts platform-infra pipelines-as-code apply --target --confirm", ], boundary: "bootstrap 是首次引导的最短入口;apply 保留为单层维护入口。两者都不得在 source PR 合并后用于交付、恢复或补跑。", mutation: "explicit-confirm-required", }; } if (scope === "compatibility-diagnostics") { return { command: "platform-infra pipelines-as-code help compatibility-diagnostics", scope: "explicit-readonly-or-connectivity-diagnostics", configTruth: configLabel, usage: [ "bun scripts/cli.ts platform-infra pipelines-as-code closeout --target --consumer --source-commit --wait [--timeout 120s]", "bun scripts/cli.ts platform-infra pipelines-as-code status --target --consumer --full", "bun scripts/cli.ts platform-infra pipelines-as-code history --target --consumer --limit 10 --full", "bun scripts/cli.ts platform-infra pipelines-as-code debug-step --target --consumer --json", ], boundary: "本 scope 只允许读取现有 PaC/Tekton/Argo 证据;不得 POST hook test、伪造 push、交付、恢复或补跑。", }; } return { command: "platform-infra pipelines-as-code plan|status|history|delivery-timing|debug-step|source-artifact", configTruth: configLabel, usage: [ "bun scripts/cli.ts platform-infra pipelines-as-code plan --target JD01", "bun scripts/cli.ts platform-infra pipelines-as-code status --target JD01 [--json|--full|--raw]", "bun scripts/cli.ts platform-infra pipelines-as-code history --target JD01 [--consumer hwlab-jd01-v03] [--limit 10]", "bun scripts/cli.ts platform-infra pipelines-as-code delivery-timing --target NC01 --consumer selfmedia-nc01 [--json]", "bun scripts/cli.ts platform-infra pipelines-as-code history --target JD01 --id ", "bun scripts/cli.ts platform-infra pipelines-as-code debug-step --target JD01 [--consumer ] [--id ] [--json]", "bun scripts/cli.ts platform-infra pipelines-as-code source-artifact check --target NC01 --consumer agentrun-nc01-v02 --source-worktree /abs/worktree", "bun scripts/cli.ts platform-infra pipelines-as-code source-artifact verify-runtime --target NC01 --consumer agentrun-nc01-v02 --source-worktree /abs/worktree --source-commit ", ], maintenanceHelp: "bun scripts/cli.ts platform-infra pipelines-as-code help platform-bootstrap", compatibilityHelp: "bun scripts/cli.ts platform-infra pipelines-as-code help compatibility-diagnostics", boundary: "唯一正式触发链:GitHub PR merge -> GitHub webhook bridge -> Gitea mirror/snapshot -> Pipelines-as-Code -> Tekton -> GitOps/Argo/k8s runtime。", }; } export function readPacConfig(selection: { readonly consumerId?: string | null; readonly selectDefault?: boolean } = {}): PacConfig { const root = materializeYamlComposition(readYamlRecord>(configFile, "platform-infra-pipelines-as-code"), { label: configLabel }).value; return parsePacConfigDocument(root, selection); } export function parsePacConfigDocument( root: Record, selection: { readonly consumerId?: string | null; readonly selectDefault?: boolean } = {}, ): PacConfig { const release = y.objectField(root, "release", ""); const deliveryProvenance = y.objectField(root, "deliveryProvenance", ""); const provenanceController = y.objectField(deliveryProvenance, "controller", "deliveryProvenance"); const provenanceAdmission = y.objectField(deliveryProvenance, "admission", "deliveryProvenance"); const capabilities = y.objectField(root, "capabilities", ""); const sentinelInternalPublish = y.objectField(capabilities, "sentinelInternalPublish", "capabilities"); const closeout = y.objectField(root, "closeout", ""); const gitOpsMirrorFlush = y.objectField(closeout, "gitOpsMirrorFlush", "closeout"); const gitea = y.objectField(root, "gitea", ""); const display = y.objectField(root, "display", ""); const deliveryTiming = y.objectField(root, "deliveryTiming", ""); const observability = y.objectField(root, "observability", ""); const observabilitySampling = y.objectField(observability, "sampling", "observability"); const exporterFailure = y.objectField(observability, "exporterFailure", "observability"); const admin = y.objectField(gitea, "admin", "gitea"); const webhook = y.objectField(gitea, "webhook", "gitea"); const defaults = y.objectField(root, "defaults", ""); const repositoryEntries = pacConfigObjectEntries(root.repositories, root.repository, "repositories", "repository"); const consumerEntries = pacConfigObjectEntries(root.consumers, root.consumer, "consumers", "consumer"); const defaultRepositoryRef = rawPacObjectId(repositoryEntries[0]?.value, repositoryEntries[0]?.path ?? "repository", "repository"); const defaultConsumerId = typeof defaults.consumerId === "string" && defaults.consumerId.length > 0 ? defaults.consumerId : rawPacObjectId(consumerEntries[0]?.value, consumerEntries[0]?.path ?? "consumer", "consumer"); const selectedConsumerIds = selectedPacConsumerIds(defaultConsumerId, selection); assertSelectedRawObjectsUnique(consumerEntries, selectedConsumerIds, "consumer"); const selectedRepositoryRefs = selectedPacRepositoryRefs(consumerEntries, selectedConsumerIds, defaultRepositoryRef); assertSelectedRawObjectsUnique(repositoryEntries, selectedRepositoryRefs, "repository"); const parsedRepositories = parsePacRepositories(repositoryEntries, selectedRepositoryRefs); const parsedConsumers = parsePacConsumers(consumerEntries, selectedConsumerIds, defaultRepositoryRef); const parsed: PacConfig = { version: y.integerField(root, "version", ""), kind: "platform-infra-pipelines-as-code", metadata: { id: y.stringField(y.objectField(root, "metadata", ""), "id", "metadata"), owner: y.stringField(y.objectField(root, "metadata", ""), "owner", "metadata"), spec: y.stringField(y.objectField(root, "metadata", ""), "spec", "metadata"), relatedIssues: y.numberArrayField(y.objectField(root, "metadata", ""), "relatedIssues", "metadata"), }, defaults: { targetId: y.stringField(defaults, "targetId", "defaults"), consumerId: defaultConsumerId, }, display: { timeZone: y.stringField(display, "timeZone", "display"), }, deliveryTiming: { endToEndBudgetSeconds: positiveInteger(deliveryTiming, "endToEndBudgetSeconds", "deliveryTiming"), configPath: `${configLabel}#deliveryTiming.endToEndBudgetSeconds`, }, observability: { configRef: y.stringField(observability, "configRef", "observability"), tracesEndpoint: urlField(observability, "tracesEndpoint", "observability"), serviceName: y.stringField(observability, "serviceName", "observability"), propagation: y.stringArrayField(observability, "propagation", "observability"), samplingMode: y.enumField(observabilitySampling, "mode", "observability.sampling", ["parentbased_traceidratio"] as const), samplingRatio: numberInRange(observabilitySampling.ratio, 0, 1, "observability.sampling.ratio"), exporterFailure: { warning: literalBoolean(exporterFailure, "warning", true, "observability.exporterFailure"), blocking: literalBoolean(exporterFailure, "blocking", false, "observability.exporterFailure"), timeoutMs: integerInRange(exporterFailure.timeoutMs, 50, 2_000, "observability.exporterFailure.timeoutMs"), }, }, release: { version: y.stringField(release, "version", "release"), manifestUrl: urlField(release, "manifestUrl", "release"), namespace: y.kubernetesNameField(release, "namespace", "release"), controllerServiceName: y.kubernetesNameField(release, "controllerServiceName", "release"), controllerServicePort: y.portField(release, "controllerServicePort", "release"), waitTimeoutSeconds: positiveInteger(release, "waitTimeoutSeconds", "release"), }, deliveryProvenance: { version: y.stringField(deliveryProvenance, "version", "deliveryProvenance"), markerAnnotation: y.stringField(deliveryProvenance, "markerAnnotation", "deliveryProvenance"), controllerIdentity: `system:serviceaccount:${y.kubernetesNameField(provenanceController, "namespace", "deliveryProvenance.controller")}:${y.kubernetesNameField(provenanceController, "serviceAccountName", "deliveryProvenance.controller")}`, policyName: y.kubernetesNameField(provenanceAdmission, "policyName", "deliveryProvenance.admission"), bindingName: y.kubernetesNameField(provenanceAdmission, "bindingName", "deliveryProvenance.admission"), }, capabilities: { sentinelInternalPublish: { enabled: y.booleanField(sentinelInternalPublish, "enabled", "capabilities.sentinelInternalPublish"), admissionProvenance: y.enumField(sentinelInternalPublish, "admissionProvenance", "capabilities.sentinelInternalPublish", ["unavailable"] as const), trackingIssue: urlField(sentinelInternalPublish, "trackingIssue", "capabilities.sentinelInternalPublish"), }, }, closeout: { gitOpsMirrorFlush: { enabled: y.booleanField(gitOpsMirrorFlush, "enabled", "closeout.gitOpsMirrorFlush"), waitTimeoutSeconds: positiveInteger(gitOpsMirrorFlush, "waitTimeoutSeconds", "closeout.gitOpsMirrorFlush"), maxAttempts: positiveInteger(gitOpsMirrorFlush, "maxAttempts", "closeout.gitOpsMirrorFlush"), }, }, targets: y.arrayOfRecords(root.targets, "targets").map(parseTarget), gitea: { configRef: y.stringField(gitea, "configRef", "gitea"), internalBaseUrl: urlField(gitea, "internalBaseUrl", "gitea"), admin: { sourceRoot: y.absolutePathField(admin, "sourceRoot", "gitea.admin"), sourceRef: y.sourceRefField(admin, "sourceRef", "gitea.admin"), format: y.enumField(admin, "format", "gitea.admin", ["line-pair"] as const), usernameLine: positiveInteger(admin, "usernameLine", "gitea.admin"), passwordLine: positiveInteger(admin, "passwordLine", "gitea.admin"), apiUsername: y.stringField(admin, "apiUsername", "gitea.admin"), }, webhook: { secretRoot: y.absolutePathField(webhook, "secretRoot", "gitea.webhook"), secretSourceRef: y.sourceRefField(webhook, "secretSourceRef", "gitea.webhook"), events: y.stringArrayField(webhook, "events", "gitea.webhook"), branch: y.stringField(webhook, "branch", "gitea.webhook"), }, }, repositories: parsedRepositories.values, consumers: parsedConsumers.values, validationWarnings: [...parsedRepositories.warnings, ...parsedConsumers.warnings], }; parsed.validationWarnings = validateConfig(parsed, selectedConsumerIds, parsed.validationWarnings); return parsed; } function selectedPacConsumerIds( defaultConsumerId: string, selection: { readonly consumerId?: string | null; readonly selectDefault?: boolean }, ): ReadonlySet { if (typeof selection.consumerId === "string" && selection.consumerId.length > 0) { return new Set([selection.consumerId.toLowerCase()]); } return selection.selectDefault === true ? new Set([defaultConsumerId.toLowerCase()]) : new Set(); } interface PacConfigObjectEntry { readonly value: unknown; readonly path: string; } function pacConfigObjectEntries(plural: unknown, singular: unknown, pluralLabel: string, singularLabel: string): PacConfigObjectEntry[] { if (plural === undefined) { if (singular === undefined) throw new Error(`${configLabel}.${singularLabel} must be an object`); return [{ value: singular, path: singularLabel }]; } if (!Array.isArray(plural)) throw new Error(`${configLabel}.${pluralLabel} must be an array`); return plural.map((value, index) => ({ value, path: `${pluralLabel}[${index}]` })); } function rawPacObjectId(value: unknown, path: string, kind: "consumer" | "repository"): string { if (!isRecord(value)) return path; if (typeof value.id === "string" && value.id.length > 0) return value.id; if (kind === "repository" && typeof value.name === "string" && value.name.length > 0) return value.name; if (kind === "consumer" && typeof value.node === "string" && value.node.length > 0 && typeof value.lane === "string" && value.lane.length > 0) { return `${value.node}-${value.lane}`; } return path; } function rawPacObjectRefs(entry: PacConfigObjectEntry, kind: "consumer" | "repository"): string[] { if (!isRecord(entry.value)) return [entry.path.toLowerCase()]; const refs = [rawPacObjectId(entry.value, entry.path, kind)]; if (kind === "repository" && typeof entry.value.name === "string" && entry.value.name.length > 0) refs.push(entry.value.name); return [...new Set(refs.map((value) => value.toLowerCase()))]; } function assertSelectedRawObjectsUnique( entries: readonly PacConfigObjectEntry[], selectedRefs: ReadonlySet, kind: "consumer" | "repository", ): void { for (const selectedRef of selectedRefs) { const matches = entries.filter((entry) => rawPacObjectRefs(entry, kind).includes(selectedRef)); if (matches.length !== 1) { throw new Error(`selected Pipelines-as-Code ${kind} ${selectedRef} must match exactly one YAML object; matched ${matches.length}`); } } } function selectedPacRepositoryRefs( consumerEntries: readonly PacConfigObjectEntry[], selectedConsumerIds: ReadonlySet, defaultRepositoryRef: string, ): ReadonlySet { const refs = new Set(); for (const entry of consumerEntries) { if (!rawPacObjectRefs(entry, "consumer").some((value) => selectedConsumerIds.has(value))) continue; const repositoryRef = isRecord(entry.value) && typeof entry.value.repositoryRef === "string" && entry.value.repositoryRef.length > 0 ? entry.value.repositoryRef : defaultRepositoryRef; refs.add(repositoryRef.toLowerCase()); } return refs; } function parsePacRepositories( entries: readonly PacConfigObjectEntry[], selectedRepositoryRefs: ReadonlySet, ): { readonly values: PacRepository[]; readonly warnings: PacConfigValidationWarning[] } { const values: PacRepository[] = []; const warnings: PacConfigValidationWarning[] = []; for (const entry of entries) { const id = rawPacObjectId(entry.value, entry.path, "repository"); try { if (!isRecord(entry.value)) throw new Error(`${configLabel}.${entry.path} must be an object`); values.push(parseRepository(entry.value, entry.path)); } catch (error) { if (rawPacObjectRefs(entry, "repository").some((value) => selectedRepositoryRefs.has(value))) throw error; warnings.push(pacConfigWarning("pac-unselected-repository-parse-invalid", "repository", id, entry.path, error)); } } return { values, warnings }; } function parsePacConsumers( entries: readonly PacConfigObjectEntry[], selectedConsumerIds: ReadonlySet, defaultRepositoryRef: string, ): { readonly values: PacConsumer[]; readonly warnings: PacConfigValidationWarning[] } { const values: PacConsumer[] = []; const warnings: PacConfigValidationWarning[] = []; for (const entry of entries) { const id = rawPacObjectId(entry.value, entry.path, "consumer"); try { if (!isRecord(entry.value)) throw new Error(`${configLabel}.${entry.path} must be an object`); values.push(parseConsumer(entry.value, entry.path, defaultRepositoryRef)); } catch (error) { if (selectedConsumerIds.has(id.toLowerCase())) throw error; warnings.push(pacConfigWarning("pac-unselected-consumer-parse-invalid", "consumer", id, entry.path, error)); } } return { values, warnings }; } function pacConfigWarning( code: PacConfigValidationWarning["code"], kind: PacConfigValidationWarning["object"]["kind"], id: string, path: string, error: unknown, ): PacConfigValidationWarning { return { code, object: { kind, id }, consumer: kind === "consumer" ? id : null, configPath: `${configLabel}#${path}`, blocking: false, evidence: pacValueEvidence(error instanceof Error ? error.message : String(error)), }; } function parseRepository(repository: Record, path: string): PacRepository { const name = y.kubernetesNameField(repository, "name", path); return { id: typeof repository.id === "string" && repository.id.length > 0 ? repository.id : name, name, namespace: y.kubernetesNameField(repository, "namespace", path), providerType: y.enumField(repository, "providerType", path, ["gitea"] as const), url: urlField(repository, "url", path), cloneUrl: urlField(repository, "cloneUrl", path), owner: y.stringField(repository, "owner", path), repo: y.stringField(repository, "repo", path), secretName: y.kubernetesNameField(repository, "secretName", path), tokenKey: y.stringField(repository, "tokenKey", path), webhookSecretKey: y.stringField(repository, "webhookSecretKey", path), concurrencyLimit: positiveInteger(repository, "concurrencyLimit", path), params: stringRecord(y.objectField(repository, "params", path), `${path}.params`), }; } function parseConsumer(consumer: Record, path: string, defaultRepositoryRef: string): PacConsumer { const id = typeof consumer.id === "string" && consumer.id.length > 0 ? consumer.id : `${y.stringField(consumer, "node", path)}-${y.stringField(consumer, "lane", path)}`; const argoBootstrap = consumer.argoBootstrap === undefined ? null : y.objectField(consumer, "argoBootstrap", path); const deliveryProvenance = consumer.deliveryProvenance === undefined ? null : parseConsumerDeliveryProvenance(y.objectField(consumer, "deliveryProvenance", path), `${path}.deliveryProvenance`); const runnerServiceAccount = consumer.runnerServiceAccount === undefined ? null : y.objectField(consumer, "runnerServiceAccount", path); return { id, node: y.stringField(consumer, "node", path), lane: y.stringField(consumer, "lane", path), namespace: y.kubernetesNameField(consumer, "namespace", path), pipeline: y.kubernetesNameField(consumer, "pipeline", path), pipelineRunPrefix: y.stringField(consumer, "pipelineRunPrefix", path), argoNamespace: y.kubernetesNameField(consumer, "argoNamespace", path), argoApplication: y.kubernetesNameField(consumer, "argoApplication", path), argoBootstrap: argoBootstrap === null ? null : { project: y.kubernetesNameField(argoBootstrap, "project", `${path}.argoBootstrap`), repoUrl: urlField(argoBootstrap, "repoUrl", `${path}.argoBootstrap`), targetRevision: y.stringField(argoBootstrap, "targetRevision", `${path}.argoBootstrap`), path: y.stringField(argoBootstrap, "path", `${path}.argoBootstrap`), destinationNamespace: y.kubernetesNameField(argoBootstrap, "destinationNamespace", `${path}.argoBootstrap`), automated: y.booleanField(argoBootstrap, "automated", `${path}.argoBootstrap`), repositoryCredential: argoBootstrap.repositoryCredential === undefined ? null : (() => { const credential = y.objectField(argoBootstrap, "repositoryCredential", `${path}.argoBootstrap`); return { secretName: y.kubernetesNameField(credential, "secretName", `${path}.argoBootstrap.repositoryCredential`), username: y.stringField(credential, "username", `${path}.argoBootstrap.repositoryCredential`), }; })(), }, deliveryProvenance, repositoryRef: typeof consumer.repositoryRef === "string" && consumer.repositoryRef.length > 0 ? consumer.repositoryRef : defaultRepositoryRef, params: consumer.params === undefined ? {} : stringRecord(y.objectField(consumer, "params", path), `${path}.params`), closeoutGitOpsMirrorFlush: y.booleanField(consumer, "closeoutGitOpsMirrorFlush", path), closeoutGitOpsMirrorLane: consumer.closeoutGitOpsMirrorLane === undefined ? null : y.enumField(consumer, "closeoutGitOpsMirrorLane", path, ["v02", "v03"] as const), sourceArtifact: consumer.sourceArtifact === undefined ? null : parseSourceArtifact(y.objectField(consumer, "sourceArtifact", path), `${path}.sourceArtifact`), runnerServiceAccount: runnerServiceAccount === null ? null : { name: y.kubernetesNameField(runnerServiceAccount, "name", `${path}.runnerServiceAccount`), automountServiceAccountToken: parseFalse(runnerServiceAccount, "automountServiceAccountToken", `${path}.runnerServiceAccount`), roleBindingName: y.kubernetesNameField(runnerServiceAccount, "roleBindingName", `${path}.runnerServiceAccount`), }, }; } function parseFalse(record: Record, key: string, path: string): false { if (y.booleanField(record, key, path) !== false) throw new Error(`${path}.${key} must be false`); return false; } function parseConsumerDeliveryProvenance(value: Record, path: string): PacConsumer["deliveryProvenance"] { const required = y.booleanField(value, "required", path); if (!required) return null; const gitOps = y.objectField(value, "gitOps", path); return { required: true, markerValue: y.stringField(value, "markerValue", path), executionServiceAccountName: y.kubernetesNameField(value, "executionServiceAccountName", path), gitOps: { repoUrl: urlField(gitOps, "repoUrl", `${path}.gitOps`), targetRevision: y.stringField(gitOps, "targetRevision", `${path}.gitOps`), }, }; } function parseSourceArtifact(value: Record, path: string): PacSourceArtifactSpec { const mode = y.enumField(value, "mode", path, ["embedded-pipeline-spec", "remote-pipeline-annotation"] as const); const renderer = y.enumField(value, "renderer", path, ["agentrun-control-plane", "hwlab-runtime-lane", "sub2rank-platform-service", "selfmedia-runtime", "pikaoa-test-runtime"] as const); const pipelineRunPath = sourceArtifactPath(y.stringField(value, "pipelineRunPath", path), `${path}.pipelineRunPath`); const pipelinePath = value.pipelinePath === undefined ? null : sourceArtifactPath(y.stringField(value, "pipelinePath", path), `${path}.pipelinePath`); const taskRunTemplate = y.objectField(value, "taskRunTemplate", path); if (mode === "embedded-pipeline-spec" && pipelinePath !== null) throw new Error(`${path}.pipelinePath is forbidden for embedded-pipeline-spec`); if (mode === "remote-pipeline-annotation" && pipelinePath === null) throw new Error(`${path}.pipelinePath is required for remote-pipeline-annotation`); if (renderer === "agentrun-control-plane" && mode !== "embedded-pipeline-spec") throw new Error(`${path}.renderer agentrun-control-plane requires embedded-pipeline-spec`); if (renderer === "hwlab-runtime-lane" && mode !== "remote-pipeline-annotation") throw new Error(`${path}.renderer hwlab-runtime-lane requires remote-pipeline-annotation`); if (renderer === "sub2rank-platform-service" && mode !== "embedded-pipeline-spec") throw new Error(`${path}.renderer sub2rank-platform-service requires embedded-pipeline-spec`); if (renderer === "selfmedia-runtime" && mode !== "embedded-pipeline-spec") throw new Error(`${path}.renderer selfmedia-runtime requires embedded-pipeline-spec`); if (renderer === "pikaoa-test-runtime" && mode !== "embedded-pipeline-spec") throw new Error(`${path}.renderer pikaoa-test-runtime requires embedded-pipeline-spec`); return { mode, renderer, configRef: y.stringField(value, "configRef", path), pipelineRunPath, pipelinePath, maxKeepRuns: positiveInteger(value, "maxKeepRuns", path), taskRunTemplate: { hostNetwork: y.booleanField(taskRunTemplate, "hostNetwork", `${path}.taskRunTemplate`), dnsPolicy: y.enumField(taskRunTemplate, "dnsPolicy", `${path}.taskRunTemplate`, ["ClusterFirst", "ClusterFirstWithHostNet", "Default", "None"] as const), fsGroup: positiveInteger(taskRunTemplate, "fsGroup", `${path}.taskRunTemplate`), }, }; } function sourceArtifactPath(value: string, path: string): string { if (value.startsWith("/") || value.split(/[\\/]/u).includes("..") || !value.endsWith(".yaml")) throw new Error(`${path} must be a worktree-relative .yaml path without ..`); return value; } async function observeSourceArtifactRuntime( config: UniDeskConfig, target: PacTarget, consumer: PacConsumer, desiredSpec: Record, desiredWrapper: Record | null, provenance: { readonly configRef: string; readonly effectiveConfigSha256: string; readonly renderer: string; readonly mode: string }, sourceCommit: string | null, ): Promise { const script = renderPacSourceArtifactRuntimeObserverShell({ namespace: consumer.namespace, pipeline: consumer.pipeline, pipelineRunPrefix: consumer.pipelineRunPrefix, desiredSpec: canonicalizePacPipelineSpec(desiredSpec), desiredWrapper: desiredWrapper === null ? null : canonicalizePacPipelineSpec(desiredWrapper), provenance, sourceCommit, }); const captured = await capture(config, target.route, ["sh"], script); if (captured.exitCode !== 0) { return unavailableSourceArtifactRuntime(pacRuntimeTransportFailureReason(captured), sourceCommit); } const parsed = parseJsonOutput(captured.stdout); if (parsed === null) return unavailableSourceArtifactRuntime("runtime-observer-invalid-json", sourceCommit); return { live: pacRuntimeSourceArtifactItem(parsed.live, { configRef: provenance.configRef, effectiveConfigSha256: provenance.effectiveConfigSha256, renderer: provenance.renderer, mode: provenance.mode, sourceCommit: null, exactName: consumer.pipeline, namePrefix: null, }), embedded: pacRuntimeSourceArtifactItem(parsed.embedded, { configRef: provenance.configRef, effectiveConfigSha256: provenance.effectiveConfigSha256, renderer: provenance.renderer, mode: provenance.mode, sourceCommit, exactName: null, namePrefix: consumer.pipelineRunPrefix, }), }; } export function pacRuntimeTransportFailureReason(captured: { readonly exitCode: number; readonly stdout: string; readonly stderr: string }): string { return `runtime-transport-exit-${captured.exitCode}:stderr(${pacValueEvidence(captured.stderr)}):stdout(${pacValueEvidence(captured.stdout)})`; } export function renderPacSourceArtifactRuntimeObserverShell(inputValue: Record): string { const input = JSON.stringify(inputValue); const observer = readFileSync(sourceArtifactRuntimeObserverFile, "utf8").trimEnd(); if (observer.includes("NODE_PAC_SOURCE_ARTIFACT_STATUS") || input.includes("JSON_PAC_SOURCE_ARTIFACT_INPUT")) throw new Error("PaC source artifact observer input contains a reserved heredoc delimiter"); return `set -eu observer_input=$(mktemp) trap 'rm -f "$observer_input"' EXIT HUP INT TERM cat >"$observer_input" <<'JSON_PAC_SOURCE_ARTIFACT_INPUT' ${input} JSON_PAC_SOURCE_ARTIFACT_INPUT PAC_SOURCE_ARTIFACT_INPUT_FILE="$observer_input" node --input-type=module <<'NODE_PAC_SOURCE_ARTIFACT_STATUS' ${observer} NODE_PAC_SOURCE_ARTIFACT_STATUS `; } export function pacRuntimeSourceArtifactItem(value: unknown, expected: { readonly configRef: string; readonly effectiveConfigSha256: string; readonly renderer: string; readonly mode: string; readonly sourceCommit: string | null; readonly exactName: string | null; readonly namePrefix: string | null; }): PacSourceArtifactRuntimeObservation["live"] { if (typeof value !== "object" || value === null || Array.isArray(value)) return unavailableSourceArtifactRuntime("runtime-observer-invalid-item", null).live; const item = value as Record; const status = item.status; if (status !== "aligned" && status !== "drifted" && status !== "missing" && status !== "unavailable") return unavailableSourceArtifactRuntime("runtime-observer-invalid-status", null).live; const drift = item.firstDrift; const observedName = typeof item.name === "string" && item.name.length <= 253 && /^[a-z0-9](?:[-a-z0-9.]*[a-z0-9])?$/u.test(item.name) ? item.name : null; const name = observedName !== null && (expected.exactName === null || observedName === expected.exactName) && (expected.namePrefix === null || observedName.startsWith(expected.namePrefix)) ? observedName : null; const provenanceAligned = item.configRef === expected.configRef && item.effectiveConfigSha256 === expected.effectiveConfigSha256 && item.renderer === expected.renderer && item.mode === expected.mode; return { status, name, canonicalSha256: typeof item.canonicalSha256 === "string" && /^sha256:[0-9a-f]{64}$/u.test(item.canonicalSha256) ? item.canonicalSha256 : null, firstDrift: typeof drift === "object" && drift !== null && !Array.isArray(drift) ? { path: pacRuntimeSafePath((drift as Record).path), expected: pacRuntimeSafeEvidence((drift as Record).expected), actual: pacRuntimeSafeEvidence((drift as Record).actual), } : null, provenanceAligned, configRef: item.configRef === expected.configRef && expected.configRef.length <= 512 && /^[A-Za-z0-9_./-]+#[A-Za-z0-9_.-]+$/u.test(expected.configRef) ? expected.configRef : null, effectiveConfigSha256: item.effectiveConfigSha256 === expected.effectiveConfigSha256 && /^sha256:[0-9a-f]{64}$/u.test(expected.effectiveConfigSha256) ? expected.effectiveConfigSha256 : null, sourceCommit: item.sourceCommit === expected.sourceCommit && (expected.sourceCommit === null || /^[0-9a-f]{40}$/u.test(expected.sourceCommit)) ? expected.sourceCommit : null, reason: typeof item.reason === "string" ? pacRuntimeSafeReason(item.reason) : null, candidateCount: typeof item.candidateCount === "number" && Number.isInteger(item.candidateCount) && item.candidateCount >= 0 ? item.candidateCount : 0, }; } export function pacRuntimeSafePath(value: unknown): string { const path = typeof value === "string" ? value : "$"; const allowedSegments = new Set([ "accessModes", "annotations", "apiVersion", "args", "command", "computeResources", "default", "description", "dnsPolicy", "env", "envFrom", "executionMode", "finally", "fsGroup", "generateName", "hostNetwork", "image", "kind", "labels", "length", "metadata", "mode", "mountPath", "name", "namespace", "onError", "operator", "optional", "params", "pipeline", "pipelineRef", "pipelineSpec", "podTemplate", "readinessProbe", "readOnly", "requests", "resources", "results", "retries", "runAfter", "script", "secret", "secretName", "securityContext", "serviceAccountName", "sidecars", "spec", "steps", "storage", "subPath", "taskRunTemplate", "taskSpec", "tasks", "timeout", "timeouts", "type", "value", "values", "volumeClaimTemplate", "volumeMounts", "volumes", "when", "workspaces", "workingDir", "wrapper", ]); if (!path.startsWith("$")) return `path(${pacValueEvidence(path)})`; const token = /(?:\.([A-Za-z0-9_-]+))|(?:\[(\d+)\])/gu; let offset = 1; for (const match of path.slice(1).matchAll(token)) { if (match.index !== offset - 1) return `path(${pacValueEvidence(path)})`; if (match[1] !== undefined && !allowedSegments.has(match[1])) return `path(${pacValueEvidence(path)})`; offset = 1 + (match.index ?? 0) + match[0].length; } return offset === path.length ? path : `path(${pacValueEvidence(path)})`; } function pacRuntimeSafeEvidence(value: unknown): string { return typeof value === "string" && /^type=[a-z]+,length=\d+,sha256=[0-9a-f]{64}$/u.test(value) ? value : pacValueEvidence(value); } export function pacRuntimeSafeReason(value: string): string { const fixed = new Set([ "source-commit-not-specified", "source-commit-run-not-found", "source-commit-identity-conflict", "pipeline-get-not-found", "pipelinerun-list-not-found", "pipelinerun-list-invalid-shape", "runtime-observer-invalid-json", "runtime-observer-invalid-item", "runtime-observer-invalid-status", "runtime-observer-not-configured", ]); if (fixed.has(value)) return value; if (/^source-commit-run-conflict:\d+$/u.test(value)) return value; if (/^(?:pipeline-get|pipelinerun-list)-command-failed:(?:unknown|\d+):type=string,length=\d+,sha256=[0-9a-f]{64}$/u.test(value)) return value; if (/^(?:runtime-observer-input-error|runtime-observer-reason):type=string,length=\d+,sha256=[0-9a-f]{64}$/u.test(value)) return value; return `runtime-observer-reason:${pacValueEvidence(value)}`; } function unavailableSourceArtifactRuntime(reason: string, sourceCommit: string | null): PacSourceArtifactRuntimeObservation { const item = { status: "unavailable" as const, name: null, canonicalSha256: null, firstDrift: null, provenanceAligned: false, configRef: null, effectiveConfigSha256: null, sourceCommit, reason, candidateCount: 0 }; return { live: item, embedded: item }; } function parseTarget(record: Record, index: number): PacTarget { const path = `targets[${index}]`; return { id: y.stringField(record, "id", path), route: y.stringField(record, "route", path), namespace: y.kubernetesNameField(record, "namespace", path), role: y.stringField(record, "role", path), enabled: y.booleanField(record, "enabled", path), }; } export function validatePacConfig(config: PacConfig, selectedConsumerIds: ReadonlySet): PacConfigValidationWarning[] { return validateConfig(config, selectedConsumerIds, []); } function validateConfig( config: PacConfig, selectedConsumerIds: ReadonlySet, initialWarnings: readonly PacConfigValidationWarning[], ): PacConfigValidationWarning[] { if (config.version !== 1) throw new Error(`${configLabel}.version must be 1`); resolveTarget(config, config.defaults.targetId); if (config.release.waitTimeoutSeconds > 55) throw new Error(`${configLabel}.release.waitTimeoutSeconds must fit the 60s trans budget`); if (config.closeout.gitOpsMirrorFlush.waitTimeoutSeconds > 55) throw new Error(`${configLabel}.closeout.gitOpsMirrorFlush.waitTimeoutSeconds must fit the 60s trans budget`); if (selectedConsumerIds.has(config.defaults.consumerId.toLowerCase())) resolveConsumer(config, config.defaults.consumerId); const warnings: PacConfigValidationWarning[] = [...initialWarnings]; const selectedRepositoryRefs = new Set(config.consumers .filter((consumer) => selectedConsumerIds.has(consumer.id.toLowerCase())) .map((consumer) => consumer.repositoryRef.toLowerCase())); const ids = new Set(); for (const repository of config.repositories) { try { if (ids.has(repository.id)) throw new Error(`${configLabel}.repositories id must be unique: ${repository.id}`); if (repository.providerType !== "gitea") throw new Error(`${configLabel}.repositories.${repository.id}.providerType must be gitea`); if (isInternalRepositoryMatchUrl(repository.url)) throw new Error(`${configLabel}.repositories.${repository.id}.url must be the public Gitea URL used in webhook payloads; keep internal service URLs in cloneUrl/params.git_read_url`); if (new URL(repository.cloneUrl).origin !== new URL(config.gitea.internalBaseUrl).origin) throw new Error(`${configLabel}.repositories.${repository.id}.cloneUrl must use the configured internal Gitea base URL`); ids.add(repository.id); } catch (error) { if (selectedRepositoryRefs.has(repository.id.toLowerCase()) || selectedRepositoryRefs.has(repository.name.toLowerCase())) throw error; warnings.push(pacConfigWarning("pac-unselected-repository-invalid", "repository", repository.id, `repositories.${repository.id}`, error)); } } const consumerIds = new Set(); const markerValues = new Set(); const reservedServiceAccounts = new Set(); for (const consumer of config.consumers) { try { if (consumerIds.has(consumer.id)) throw new Error(`${configLabel}.consumers id must be unique: ${consumer.id}`); validateConsumerConfig(config, consumer, markerValues, reservedServiceAccounts); consumerIds.add(consumer.id); if (consumer.deliveryProvenance !== null) { markerValues.add(consumer.deliveryProvenance.markerValue); reservedServiceAccounts.add(`${consumer.node.toLowerCase()}\u0000${consumer.deliveryProvenance.executionServiceAccountName}`); } } catch (error) { if (selectedConsumerIds.has(consumer.id.toLowerCase())) throw error; warnings.push(pacConfigWarning("pac-unselected-consumer-invalid", "consumer", consumer.id, `consumers.${consumer.id}`, error)); } } if (!config.gitea.webhook.events.includes("push")) throw new Error(`${configLabel}.gitea.webhook.events must include push`); validateTimeZone(config.display.timeZone, `${configLabel}.display.timeZone`); return warnings; } function validateConsumerConfig( config: PacConfig, consumer: PacConsumer, markerValues: Set, reservedServiceAccounts: Set, ): void { const repository = resolveRepository(config, consumer.repositoryRef); const params = consumerParams(repository, consumer); if (consumer.id.startsWith("sentinel-")) { if (repository.params.otel_traces_endpoint !== config.observability.tracesEndpoint) { throw new Error(`${configLabel}.repositories.${repository.id}.params.otel_traces_endpoint must match observability.tracesEndpoint`); } if (repository.params.otel_sampling_ratio !== String(config.observability.samplingRatio)) { throw new Error(`${configLabel}.repositories.${repository.id}.params.otel_sampling_ratio must match observability.sampling.ratio`); } if (repository.params.otel_exporter_timeout_ms !== String(config.observability.exporterFailure.timeoutMs)) { throw new Error(`${configLabel}.repositories.${repository.id}.params.otel_exporter_timeout_ms must match observability.exporterFailure.timeoutMs`); } } if (repository.namespace !== consumer.namespace) throw new Error(`${configLabel}.consumers.${consumer.id}.namespace must match repository namespace`); if (consumer.deliveryProvenance !== null) { if (consumer.sourceArtifact === null) throw new Error(`${configLabel}.consumers.${consumer.id}.deliveryProvenance requires sourceArtifact ownership`); if (markerValues.has(consumer.deliveryProvenance.markerValue)) throw new Error(`${configLabel}.consumers deliveryProvenance.markerValue must be unique: ${consumer.deliveryProvenance.markerValue}`); if (consumer.deliveryProvenance.executionServiceAccountName === "default") { throw new Error(`${configLabel}.consumers.${consumer.id}.deliveryProvenance.executionServiceAccountName must not reserve the shared default ServiceAccount`); } const serviceAccountKey = `${consumer.node.toLowerCase()}\u0000${consumer.deliveryProvenance.executionServiceAccountName}`; if (reservedServiceAccounts.has(serviceAccountKey)) { throw new Error(`${configLabel}.consumers deliveryProvenance.executionServiceAccountName must be unique per target: ${consumer.deliveryProvenance.executionServiceAccountName}`); } if (params.service_account !== consumer.deliveryProvenance.executionServiceAccountName) { throw new Error(`${configLabel}.consumers.${consumer.id}.deliveryProvenance.executionServiceAccountName must match effective params.service_account`); } if (consumer.argoBootstrap !== null && (consumer.argoBootstrap.repoUrl !== consumer.deliveryProvenance.gitOps.repoUrl || consumer.argoBootstrap.targetRevision !== consumer.deliveryProvenance.gitOps.targetRevision)) { throw new Error(`${configLabel}.consumers.${consumer.id}.deliveryProvenance.gitOps must match argoBootstrap source identity`); } if (consumer.runnerServiceAccount !== null && consumer.runnerServiceAccount.name !== consumer.deliveryProvenance.executionServiceAccountName) { throw new Error(`${configLabel}.consumers.${consumer.id}.runnerServiceAccount.name must match deliveryProvenance.executionServiceAccountName`); } } if (consumer.sourceArtifact?.renderer === "sub2rank-platform-service" && consumer.runnerServiceAccount === null) { throw new Error(`${configLabel}.consumers.${consumer.id}.runnerServiceAccount is required for sub2rank-platform-service`); } if (consumer.sourceArtifact?.renderer === "selfmedia-runtime" || consumer.sourceArtifact?.renderer === "pikaoa-test-runtime") { if (consumer.runnerServiceAccount === null) throw new Error(`${configLabel}.consumers.${consumer.id}.runnerServiceAccount is required for private GitOps runtime renderers`); if (consumer.argoBootstrap?.repositoryCredential === null || consumer.argoBootstrap?.repositoryCredential === undefined) { throw new Error(`${configLabel}.consumers.${consumer.id}.argoBootstrap.repositoryCredential is required for the private repository`); } if (consumer.closeoutGitOpsMirrorFlush) throw new Error(`${configLabel}.consumers.${consumer.id}.closeoutGitOpsMirrorFlush must be false for Gitea writeback GitOps`); if (params.gitops_secret_name !== repository.secretName) throw new Error(`${configLabel}.consumers.${consumer.id}.effective params.gitops_secret_name must match repository secretName`); if (params.gitops_username !== consumer.argoBootstrap.repositoryCredential.username) throw new Error(`${configLabel}.consumers.${consumer.id}.Argo and Tekton Gitea usernames must match`); if (params.gitops_read_url !== consumer.argoBootstrap.repoUrl) throw new Error(`${configLabel}.consumers.${consumer.id}.Argo repoUrl must match effective params.gitops_read_url`); } } function numberInRange(value: unknown, minimum: number, maximum: number, path: string): number { if (typeof value !== "number" || !Number.isFinite(value) || value < minimum || value > maximum) { throw new Error(`${configLabel}.${path} must be a number from ${minimum} to ${maximum}`); } return value; } function integerInRange(value: unknown, minimum: number, maximum: number, path: string): number { const parsed = numberInRange(value, minimum, maximum, path); if (!Number.isInteger(parsed)) throw new Error(`${configLabel}.${path} must be an integer`); return parsed; } function literalBoolean(value: Record, key: string, expected: T, path: string): T { if (value[key] !== expected) throw new Error(`${configLabel}.${path}.${key} must be ${String(expected)}`); return expected; } function resolveTarget(config: PacConfig, targetId: string | null): PacTarget { const id = targetId ?? config.defaults.targetId; const target = config.targets.find((item) => item.id.toLowerCase() === id.toLowerCase()); if (target === undefined) throw new Error(`unknown Pipelines-as-Code target ${id}; known targets: ${config.targets.map((item) => item.id).join(", ")}`); if (!target.enabled) throw new Error(`Pipelines-as-Code target ${target.id} is disabled in ${configLabel}`); return target; } function resolveConsumer(config: PacConfig, consumerId: string | null): PacConsumer { const id = consumerId ?? config.defaults.consumerId; const matches = config.consumers.filter((item) => item.id.toLowerCase() === id.toLowerCase()); if (matches.length !== 1) throw new Error(`Pipelines-as-Code consumer ${id} must resolve exactly once; matched ${matches.length}; known consumers: ${config.consumers.map((item) => item.id).join(", ")}`); return matches[0] as PacConsumer; } function resolveRepository(config: PacConfig, repositoryRef: string): PacRepository { const matches = config.repositories.filter((item) => item.id === repositoryRef || item.name === repositoryRef); if (matches.length !== 1) throw new Error(`Pipelines-as-Code repository ${repositoryRef} must resolve exactly once; matched ${matches.length}; known repositories: ${config.repositories.map((item) => item.id).join(", ")}`); return matches[0] as PacRepository; } function consumerParams(repository: PacRepository, consumer: PacConsumer): Record { return { ...repository.params, ...consumer.params }; } export function validPacConsumers(config: PacConfig): PacConsumer[] { const invalidConsumers = new Set(config.validationWarnings .filter((warning) => warning.object.kind === "consumer") .map((warning) => warning.object.id.toLowerCase())); const invalidRepositories = new Set(config.validationWarnings .filter((warning) => warning.object.kind === "repository") .map((warning) => warning.object.id.toLowerCase())); for (const repository of config.repositories) { if (invalidRepositories.has(repository.id.toLowerCase()) || invalidRepositories.has(repository.name.toLowerCase())) { invalidRepositories.add(repository.id.toLowerCase()); invalidRepositories.add(repository.name.toLowerCase()); } } return config.consumers.filter((consumer) => ( !invalidConsumers.has(consumer.id.toLowerCase()) && !invalidRepositories.has(consumer.repositoryRef.toLowerCase()) )); } function plan(options: CommonOptions): Record { const pac = readPacConfig({ consumerId: options.consumerId, selectDefault: true }); const target = resolveTarget(pac, options.targetId); const consumer = resolveConsumer(pac, options.consumerId); const repository = resolveRepository(pac, consumer.repositoryRef); const secrets = secretSummaries(pac); return { ok: true, action: "platform-infra-pipelines-as-code-plan", mutation: false, target: targetSummary(target), config: configSummary(pac, consumer, repository), release: pac.release, repository: repositorySummary(repository), consumer: consumerObservationSummary(consumer), secrets, policy: policyChecks(repository), warnings: pac.validationWarnings, next: nextCommands(target.id, consumer.id, pac.defaults.consumerId), }; } async function apply(config: UniDeskConfig, options: ApplyOptions): Promise> { const pac = readPacConfig({ consumerId: options.consumerId, selectDefault: true }); const target = resolveTarget(pac, options.targetId); const consumer = resolveConsumer(pac, options.consumerId); const repository = resolveRepository(pac, consumer.repositoryRef); const secrets = ensureSecrets(pac, !options.dryRun, options.dryRun); const releaseManifest = options.dryRun ? "" : await fetchReleaseManifest(pac); const result = await capture(config, target.route, ["sh"], remoteScript("apply", pac, target, repository, consumer, options, secrets, releaseManifest)); const parsed = parseJsonOutput(result.stdout); return { ok: result.exitCode === 0 && parsed?.ok === true, action: "platform-infra-pipelines-as-code-apply", mutation: options.dryRun ? false : parsed?.mutation !== false, mode: options.dryRun ? "dry-run" : "confirmed", target: targetSummary(target), config: compactConfigSummary(pac, consumer, repository), release: { version: pac.release.version, manifestUrl: pac.release.manifestUrl }, repository: repositorySummary(repository), consumer: consumerObservationSummary(consumer), secrets: secretSummaries(pac), remote: parsed ?? compactCapture(result, { full: true }), warnings: pac.validationWarnings, next: nextCommands(target.id, consumer.id, pac.defaults.consumerId), }; } async function bootstrap(config: UniDeskConfig, options: ApplyOptions): Promise> { const pac = readPacConfig({ consumerId: options.consumerId, selectDefault: true }); const target = resolveTarget(pac, options.targetId); const consumer = resolveConsumer(pac, options.consumerId); if (consumer.node.toLowerCase() !== target.id.toLowerCase()) { throw new Error(`Pipelines-as-Code consumer ${consumer.id} belongs to ${consumer.node}, not target ${target.id}`); } const repository = resolveRepository(pac, consumer.repositoryRef); let mirror: ReturnType; try { mirror = resolvePacBootstrapMirrorRepository(readGiteaConfig(), target.id, { ...repository, sourceBranch: consumerParams(repository, consumer).source_branch, }); } catch (error) { return { ...pacBootstrapYamlMatchFailure(target.id, consumer.id, error), warnings: pac.validationWarnings }; } const githubPreflight = record(await runGiteaMirrorBootstrapPreflight(config, target.id, mirror.key)); if (githubPreflight.ok !== true) { return bootstrapResult(target, consumer, repository, mirror, options, githubPreflight, { ok: false, mutation: false, mode: "skipped", githubPreflight, valuesPrinted: false }, { ok: false, mutation: false, mode: "skipped", valuesPrinted: false }, pac.validationWarnings); } const giteaArgs = ["mirror", "bootstrap", "--target", target.id, "--repo", mirror.key, ...(options.confirm ? ["--confirm"] : []), "--full"]; const giteaBootstrap = record(await runPlatformInfraGiteaCommand(config, giteaArgs)); if (options.confirm && giteaBootstrap.ok !== true) { return bootstrapResult(target, consumer, repository, mirror, options, githubPreflight, giteaBootstrap, { ok: false, mutation: false, mode: "skipped", error: "gitea-bootstrap-failed", valuesPrinted: false, }, pac.validationWarnings); } const pacApply = await apply(config, options); return bootstrapResult(target, consumer, repository, mirror, options, githubPreflight, giteaBootstrap, pacApply, pac.validationWarnings); } function bootstrapResult( target: PacTarget, consumer: PacConsumer, repository: PacRepository, mirror: ReturnType, options: ApplyOptions, githubPreflight: Record, giteaBootstrap: Record, pacApply: Record, warnings: readonly PacConfigValidationWarning[], ): Record { const remote = record(pacApply.remote); const repositoryMissing = remote.error === "gitea-repository-missing"; const pacReady = pacApply.ok === true || (!options.confirm && repositoryMissing); const ok = giteaBootstrap.ok === true && pacReady; const confirm = `bun scripts/cli.ts platform-infra pipelines-as-code bootstrap --target ${target.id} --consumer ${consumer.id} --confirm`; return { ok, action: "platform-infra-pipelines-as-code-bootstrap", mutation: giteaBootstrap.mutation === true || pacApply.mutation === true, mode: options.confirm ? "confirmed" : "dry-run", target: targetSummary(target), consumer: consumerObservationSummary(consumer), repository: repositorySummary(repository), mirrorRepository: { key: mirror.key, upstreamRepository: mirror.upstream.repository, upstreamBranch: mirror.upstream.branch, owner: mirror.gitea.owner, repo: mirror.gitea.name, valuesPrinted: false, }, githubPreflight, steps: [ { id: "gitea-repository", ok: giteaBootstrap.ok === true, mutation: giteaBootstrap.mutation === true, mode: giteaBootstrap.mode, valuesPrinted: false }, { id: "pac-tekton-gitops", ok: pacReady, mutation: pacApply.mutation === true, mode: pacApply.mode, repositoryMissing, valuesPrinted: false }, ], giteaBootstrap, pipelinesAsCodeApply: pacApply, warnings, next: options.confirm ? { deliveryTrigger: `Merge the prepared GitHub PR for ${mirror.upstream.repository}@${mirror.upstream.branch}; the automatic chain owns mirror, PaC, Tekton, GitOps, Argo, and runtime convergence.`, investigate: `bun scripts/cli.ts platform-infra pipelines-as-code status --target ${target.id} --consumer ${consumer.id}`, } : { confirm, boundary: "Run confirm only before the first source PR merge; do not use bootstrap as post-merge recovery.", }, valuesPrinted: false, }; } async function status(config: UniDeskConfig, options: CommonOptions): Promise> { const pac = readPacConfig({ consumerId: options.consumerId, selectDefault: true }); const target = resolveTarget(pac, options.targetId); const consumer = resolveConsumer(pac, options.consumerId); const repository = resolveRepository(pac, consumer.repositoryRef); const secrets = ensureSecrets(pac, false); const result = await capture(config, target.route, ["sh"], remoteScript("status", pac, target, repository, consumer, { ...options, confirm: false, dryRun: true, wait: false }, secrets, "")); const parsed = parseJsonOutput(result.stdout); const summary = parsed === null ? null : statusSummary(parsed); const deliveryAuthority = resolveCicdDeliveryAuthority({ consumerId: consumer.id, node: consumer.node, lane: consumer.lane }); const deliveryBudget = observePacStatusDeliveryBudget({ policy: pac.deliveryTiming, targetId: target.id, consumerId: consumer.id, summary }); const deliveryWarning = record(deliveryBudget.warning); return { ok: result.exitCode === 0 && summary?.ready === true && deliveryAuthority.kind === "pac-pr-merge", action: "platform-infra-pipelines-as-code-status", mutation: false, target: targetSummary(target), config: compactConfigSummary(pac, consumer, repository), consumer: consumerObservationSummary(consumer), deliveryAuthority, coverage: consumerCoverage(pac, target.id), summary, deliveryBudget, remote: parsed === null ? compactCapture(result, { full: true }) : options.raw ? parsed : undefined, warnings: [...pac.validationWarnings, ...(Object.keys(deliveryWarning).length === 0 ? [] : [deliveryWarning])], next: nextCommands(target.id, consumer.id, pac.defaults.consumerId), }; } export async function getPlatformInfraPipelinesAsCodeNodeStatus(config: UniDeskConfig, options: PipelinesAsCodeNodeStatusOptions): Promise> { const pac = readPacConfig(); const target = resolveTarget(pac, options.targetId ?? options.nodeId); const nodeId = options.nodeId ?? target.id; const consumers = validPacConsumers(pac).filter((consumer) => consumer.node.toLowerCase() === nodeId.toLowerCase()); const startedAt = Date.now(); const statuses = await Promise.all(consumers.map(async (consumer) => { const repository = resolveRepository(pac, consumer.repositoryRef); try { const current = await status(config, { targetId: target.id, consumerId: consumer.id, full: false, raw: false, json: false }); return nodeStatusRow(target.id, consumer, repository, current); } catch (error) { const message = error instanceof Error ? error.message : String(error); return { consumer: consumer.id, node: consumer.node, lane: consumer.lane, repository: `${repository.owner}/${repository.repo}`, namespace: consumer.namespace, pipeline: consumer.pipeline, argoApplication: consumer.argoApplication, ready: false, ciReady: false, argoReady: false, runtimeReady: false, diagnosticsReady: false, reason: "status-read-failed", hint: message, error: message, valuesPrinted: false, }; } })); const ready = statuses.every((row) => row.ready === true); const configured = statuses.length > 0; return { ok: configured && ready, action: "cicd-node-status", mutation: false, node: nodeId, target: targetSummary(target), config: { path: configLabel, source: "Gitea Repository CR + Tekton PipelineRun/TaskRun + Argo + runtime live objects.", displayTimeZone: pac.display.timeZone, valuesPrinted: false, }, consumers: statuses, warnings: pac.validationWarnings, summary: { ready: configured && ready, status: configured ? (ready ? "ready" : "blocked") : "warning", code: configured ? (ready ? "cicd-node-ready" : "cicd-node-blocked") : "cicd-node-no-consumers", hint: configured ? null : `no Pipelines-as-Code consumers are configured for node ${nodeId}`, total: statuses.length, readyCount: statuses.filter((row) => row.ready === true).length, blockedCount: statuses.filter((row) => row.ready !== true).length, elapsedMs: Date.now() - startedAt, valuesPrinted: false, }, next: pacNodeReadOnlyNext(target.id), details: options.full || options.raw ? statuses : undefined, valuesPrinted: false, }; } async function closeout(config: UniDeskConfig, options: CloseoutOptions): Promise> { const pac = readPacConfig({ consumerId: options.consumerId, selectDefault: true }); const target = resolveTarget(pac, options.targetId); const consumer = resolveConsumer(pac, options.consumerId); const startedAt = Date.now(); let current: Record = {}; let phase: "ci" | "runtime" = "ci"; const waitResult = await runReadOnlyLongPoll({ timeoutMs: options.wait ? options.timeoutMs : 1, observe: async () => { current = await observeCloseoutStatus(config, options, target, consumer, phase, 1, startedAt); phase = closeoutCiReady(current, options.sourceCommit) ? "runtime" : "ci"; return current; }, completed: (value) => !options.wait || closeoutWaitComplete(value, options.sourceCommit), waitForChange: async (remainingMs) => await waitForCloseoutChange(config, target, consumer, phase, remainingMs), cursor: closeoutCursor, }); current = waitResult.value; let summary = record(current.summary); let latest = record(summary.latestPipelineRun); let diagnostics = record(summary.diagnostics); let observedSourceCommit = observedCloseoutSourceCommit(latest, diagnostics); let sourceMatched = options.sourceCommit === null || observedSourceCommit === options.sourceCommit; let gitOpsMirrorFlush: Record = { ok: true, enabled: false, configured: consumer.closeoutGitOpsMirrorFlush, required: false, applicable: false, mode: "automatic-chain-owned-observation", executed: false, reason: "PaC migrated delivery owns GitOps publication and mirror persistence; compatibility closeout is read-only.", configSource: configLabel, valuesPrinted: false, }; summary = record(current.summary); latest = record(summary.latestPipelineRun); diagnostics = record(summary.diagnostics); observedSourceCommit = observedCloseoutSourceCommit(latest, diagnostics); sourceMatched = options.sourceCommit === null || observedSourceCommit === options.sourceCommit; const baseReady = current.ok === true && sourceMatched; gitOpsMirrorFlush = { ...record(gitOpsMirrorFlush), afterSummary: summary, valuesPrinted: false, }; const ready = baseReady; printCloseoutProgress(target, consumer, "closeout", ready ? "completed" : "blocked", startedAt, { ready, sourceMatched, diagnosticCode: diagnostics.code, timedOut: waitResult.timedOut, observations: waitResult.observations, }); return { ok: ready, action: "platform-infra-pipelines-as-code-closeout", mutation: false, target: targetSummary(target), consumer, sourceCommit: options.sourceCommit, observedSourceCommit, sourceMatched, ready, timedOut: waitResult.timedOut, elapsedMs: waitResult.elapsedMs, cursor: waitResult.cursor, wait: { requested: options.wait, completed: waitResult.completed, timedOut: waitResult.timedOut, cancelled: waitResult.cancelled, observations: waitResult.observations, elapsedMs: waitResult.elapsedMs, budgetSeconds: options.timeoutMs / 1000, budgetScope: "whole-observation", source: options.wait ? "cli --timeout or default 120s" : "single observation", identity: { target: target.id, consumer: consumer.id, pipelineRun: latest.name ?? null, sourceCommit: options.sourceCommit, valuesPrinted: false }, cursor: waitResult.cursor, valuesPrinted: false, }, summary, status: current, gitOpsMirrorFlush, blocker: ready ? null : closeoutBlocker(current, options.sourceCommit, observedSourceCommit, sourceMatched, gitOpsMirrorFlush), next: { ...nextCommands(target.id, consumer.id, pac.defaults.consumerId), wait: `bun scripts/cli.ts platform-infra pipelines-as-code closeout --target ${target.id} --consumer ${consumer.id}${options.sourceCommit === null ? "" : ` --source-commit ${options.sourceCommit}`} --wait --timeout 120s`, }, valuesPrinted: false, }; } async function waitForCloseoutChange(config: UniDeskConfig, target: PacTarget, consumer: PacConsumer, phase: "ci" | "runtime", remainingMs: number): Promise { const timeoutSeconds = Math.max(1, Math.ceil(remainingMs / 1000)); const args = phase === "ci" ? ["kubectl", "-n", consumer.namespace, "get", "pipelinerun", "-l", `tekton.dev/pipeline=${consumer.pipeline}`, "--watch-only", `--request-timeout=${timeoutSeconds}s`, "-o", "name"] : ["kubectl", "-n", consumer.argoNamespace, "get", "application", consumer.argoApplication, "--watch-only", `--request-timeout=${timeoutSeconds}s`, "-o", "name"]; const script = [ "set -euo pipefail", kubernetesWatchOneEventShellFunction(), `unidesk_watch_one_event ${args.map(shQuote).join(" ")}`, ].join("\n"); const result = await capture(config, target.route, ["bash"], script, { runtimeTimeoutMs: readOnlyLongPollTransportTimeoutMs(remainingMs) }); if (result.exitCode !== 0) throw new Error(`CI/CD ${phase} watch disconnected: exit=${result.exitCode} stderr=${stringValue(compactCapture(result, { full: true }).stderrTail)}`); } function closeoutWaitComplete(value: Record, sourceCommit: string | null): boolean { if (closeoutReady(value, sourceCommit)) return true; const latest = record(record(value.summary).latestPipelineRun); const terminal = stringValue(latest.status, stringValue(latest.reason, "")).toLowerCase(); return ["failed", "failure", "false", "cancelled", "canceled"].some((status) => terminal.includes(status)); } function closeoutCursor(value: Record): string { const summary = record(value.summary); const latest = record(summary.latestPipelineRun); const argo = record(summary.argo); const runtime = record(summary.runtime); return [latest.name, latest.status, latest.reason, argo.revision, argo.sync, argo.health, runtime.digest, runtime.readyReplicas] .map((item) => stringValue(item, "-")) .join(":"); } async function observeCloseoutStatus(config: UniDeskConfig, options: CloseoutOptions, target: PacTarget, consumer: PacConsumer, phase: "ci" | "runtime", attempt: number, startedAt: number): Promise> { printCloseoutProgress(target, consumer, `${phase}-status`, "started", startedAt, { attempt }); const current = await status(config, options); const summary = record(current.summary); const latest = record(summary.latestPipelineRun); const diagnostics = record(summary.diagnostics); printCloseoutProgress(target, consumer, `${phase}-status`, "observed", startedAt, { attempt, ready: current.ok === true, pipelineRun: latest.name, sourceCommit: observedCloseoutSourceCommit(latest, diagnostics), diagnosticCode: diagnostics.code, }); return current; } function printCloseoutProgress(target: PacTarget, consumer: PacConsumer, stage: string, status: string, startedAt: number, detail: Record): void { process.stderr.write(`${JSON.stringify({ event: "platform-infra.pipelines-as-code.closeout.progress", stage, status, target: target.id, consumer: consumer.id, elapsedMs: Date.now() - startedAt, ...detail, valuesPrinted: false, })}\n`); } async function history(config: UniDeskConfig, options: HistoryOptions): Promise> { const pac = readPacConfig({ consumerId: options.consumerId }); const target = resolveTarget(pac, options.targetId); const selection = resolvePacHistoryConsumerSelection(validPacConsumers(pac), target.id, options.consumerId, options.detailId); const selectedConsumers = selection.selectedConsumerIds.map((id) => resolveConsumer(pac, id)); const firstConsumer = selectedConsumers[0]; if (firstConsumer === undefined) throw new Error("no Pipelines-as-Code consumers are configured"); const firstRepository = resolveRepository(pac, firstConsumer.repositoryRef); const secrets = ensureSecrets(pac, false); const result = await capture(config, target.route, ["sh"], remoteScript("history", pac, target, firstRepository, firstConsumer, { ...options, confirm: false, dryRun: true, wait: false }, secrets, "", selectedConsumers)); const parsed = parseJsonOutput(result.stdout); const remote = parsed ?? compactCapture(result, { full: true }); const historyErrors = arrayRecords(record(remote).historyErrors); const rows = arrayRecords(record(remote).rows); const deliveryBudget = observePacHistoryDeliveryBudget({ policy: pac.deliveryTiming, targetId: target.id, consumerIds: selectedConsumers.map((consumer) => consumer.id), defaultConsumerId: pac.defaults.consumerId, rows, }); const deliveryWarnings = arrayRecords(deliveryBudget.observations) .map((observation) => record(observation.warning)) .filter((warning) => Object.keys(warning).length > 0); return { ok: result.exitCode === 0 && parsed?.ok !== false && historyErrors.length === 0, action: "platform-infra-pipelines-as-code-history", mutation: false, target: targetSummary(target), config: { path: configLabel, source: "Gitea Repository CR + Tekton PipelineRun/TaskRun live objects; no UniDesk-owned history store is written.", limitPerConsumer: options.limit, displayTimeZone: pac.display.timeZone, valuesPrinted: false, }, consumers: selectedConsumers.map((consumer) => consumer.id), detailId: options.detailId, rows, deliveryBudget, historyErrors, warnings: [...pac.validationWarnings, ...deliveryWarnings], controlPlane: parsed === null ? undefined : { crdPresent: parsed.crdPresent === true, controllerReady: parsed.controllerReady, repositoryCondition: parsed.repositoryCondition, repository: record(parsed.repository), consumerRows: arrayRecords(parsed.consumerRows), admissionProvenance: record(parsed.admissionProvenance), webhookCount: arrayRecords(parsed.webhooks).length, valuesPrinted: false, }, remote: parsed === null || options.raw ? remote : undefined, next: selection.nextConsumerId === null ? pacNodeReadOnlyNext(target.id) : pacReadOnlyNext(target.id, selection.nextConsumerId), }; } export function resolvePacHistoryConsumerSelection( consumers: readonly PacHistoryConsumerIdentity[], targetId: string, requestedConsumerId: string | null, detailId: string | null, ): PacHistoryConsumerSelection { const targetConsumers = consumers.filter((consumer) => consumer.node.toLowerCase() === targetId.toLowerCase()); if (targetConsumers.length === 0) throw new Error(`no Pipelines-as-Code consumers are configured for target ${targetId}`); const requestedConsumer = requestedConsumerId === null ? null : consumers.find((consumer) => consumer.id.toLowerCase() === requestedConsumerId.toLowerCase()) ?? null; if (requestedConsumerId !== null && requestedConsumer === null) { throw new Error(`unknown Pipelines-as-Code consumer ${requestedConsumerId}`); } if (requestedConsumer !== null && requestedConsumer.node.toLowerCase() !== targetId.toLowerCase()) { throw new Error(`Pipelines-as-Code consumer ${requestedConsumer.id} belongs to ${requestedConsumer.node}, not target ${targetId}`); } if (detailId !== null) { const matches = targetConsumers.filter((consumer) => detailId.startsWith(consumer.pipelineRunPrefix)); if (matches.length !== 1) { throw new Error(`PipelineRun ${detailId} must match exactly one consumer on target ${targetId}; matched ${matches.length}`); } const matched = matches[0] as PacHistoryConsumerIdentity; if (requestedConsumer !== null && requestedConsumer.id.toLowerCase() !== matched.id.toLowerCase()) { throw new Error(`PipelineRun ${detailId} belongs to ${matched.id}, not requested consumer ${requestedConsumer.id}`); } return { selectedConsumerIds: [matched.id], nextConsumerId: matched.id }; } if (requestedConsumer !== null) { return { selectedConsumerIds: [requestedConsumer.id], nextConsumerId: requestedConsumer.id }; } return { selectedConsumerIds: targetConsumers.map((consumer) => consumer.id), nextConsumerId: targetConsumers.length === 1 ? (targetConsumers[0] as PacHistoryConsumerIdentity).id : null, }; } async function debugStep(config: UniDeskConfig, options: HistoryOptions): Promise> { const pac = readPacConfig({ consumerId: options.consumerId }); const target = resolveTarget(pac, options.targetId); const selection = resolvePacHistoryConsumerSelection(validPacConsumers(pac), target.id, options.consumerId, options.detailId); if (selection.selectedConsumerIds.length !== 1) { throw new Error("debug-step requires --consumer or --id resolving exactly one Pipelines-as-Code consumer"); } const consumer = resolveConsumer(pac, selection.selectedConsumerIds[0] ?? null); const repository = resolveRepository(pac, consumer.repositoryRef); const result = await capture(config, target.route, ["sh"], remoteScript("debug-step", pac, target, repository, consumer, { ...options, confirm: false, dryRun: true, wait: false }, emptySecretMaterial(), "")); const parsed = parseJsonOutput(result.stdout); const fixtureChecks = parsed === null ? [] : arrayRecords(parsed.checks); const fixtureDisclosure = pacDebugFixtureDisclosure(fixtureChecks, options.detailId); return { ok: result.exitCode === 0 && parsed?.ok === true, action: "platform-infra-pipelines-as-code-debug-step", mutation: false, target: targetSummary(target), consumer: consumer.id, detailId: options.detailId, evaluator: "scripts/native/cicd/pac-status-evaluator.cjs", fixtureGate: { ...fixtureDisclosure.fixtureGate, ok: parsed !== null && fixtureDisclosure.fixtureGate.ok === true }, checks: fixtureDisclosure.checks, realRun: parsed === null ? null : parsed.realRun ?? null, remote: parsed === null || options.raw ? parsed ?? compactCapture(result, { full: true }) : undefined, next: nextCommands(target.id, consumer.id, pac.defaults.consumerId), valuesPrinted: false, }; } export function pacDebugFixtureDisclosure(fixtureChecks: readonly Record[], detailId: string | null): { readonly fixtureGate: Record; readonly checks: readonly Record[]; } { const failedChecks = fixtureChecks.filter((item) => item.ok !== true); return { fixtureGate: { ok: failedChecks.length === 0, total: fixtureChecks.length, passed: fixtureChecks.length - failedChecks.length, failed: failedChecks.length, disclosure: detailId === null ? "all-checks" : "failed-checks-only-for-id-specific-debug", valuesPrinted: false, }, checks: detailId === null ? fixtureChecks : failedChecks, }; } async function fetchReleaseManifest(pac: PacConfig): Promise { const response = await fetch(pac.release.manifestUrl); if (!response.ok) throw new Error(`failed to fetch ${pac.release.manifestUrl}: HTTP ${response.status}`); const text = await response.text(); if (!text.includes("repositories.pipelinesascode.tekton.dev")) throw new Error(`${pac.release.manifestUrl} did not contain the Repository CRD`); return text; } function remoteScript(action: "apply" | "status" | "history" | "debug-step", pac: PacConfig, target: PacTarget, repository: PacRepository, consumer: PacConsumer, options: ApplyOptions | HistoryOptions, secrets: SecretMaterial, releaseManifest: string, historyConsumers: PacConsumer[] = [consumer]): string { const webhookUrl = `${pac.gitea.internalBaseUrl.replace(/\/+$/u, "").replace(/gitea-http\.[^.]+\.svc\.cluster\.local:3000/u, `${pac.release.controllerServiceName}.${pac.release.namespace}.svc.cluster.local:${pac.release.controllerServicePort}`)}`; const admissionIdentity = pacAdmissionDesiredIdentity(target.id); const rbacIdentity = pacConsumerRbacDesiredIdentity(target.id); const consumerConfig = historyConsumerConfig(pac, consumer); const env: Record = { UNIDESK_PAC_ACTION: action, UNIDESK_PAC_EVALUATOR_B64: Buffer.from(readFileSync(evaluatorFile, "utf8"), "utf8").toString("base64"), UNIDESK_PAC_FIELD_MANAGER: fieldManager, UNIDESK_PAC_TARGET_ID: target.id, UNIDESK_PAC_TARGET_NAMESPACE: consumer.namespace, UNIDESK_PAC_CONSUMER_ID: consumer.id, UNIDESK_PAC_RELEASE_NAMESPACE: pac.release.namespace, UNIDESK_PAC_RELEASE_MANIFEST_B64: Buffer.from(releaseManifest, "utf8").toString("base64"), UNIDESK_PAC_CONTROLLER_SERVICE_NAME: pac.release.controllerServiceName, UNIDESK_PAC_WAIT_TIMEOUT_SECONDS: String(pac.release.waitTimeoutSeconds), UNIDESK_PAC_DRY_RUN: "dryRun" in options && options.dryRun ? "1" : "0", UNIDESK_PAC_GITEA_BASE_URL: pac.gitea.internalBaseUrl.replace(/\/+$/u, ""), UNIDESK_PAC_GITEA_ADMIN_USERNAME: secrets.adminUsername, UNIDESK_PAC_GITEA_ADMIN_PASSWORD: secrets.adminPassword, UNIDESK_PAC_GITEA_API_USERNAME: pac.gitea.admin.apiUsername, UNIDESK_PAC_GITEA_OWNER: repository.owner, UNIDESK_PAC_GITEA_REPO: repository.repo, UNIDESK_PAC_WEBHOOK_URL: webhookUrl, UNIDESK_PAC_WEBHOOK_SECRET: secrets.webhookSecret, UNIDESK_PAC_REPOSITORY_NAME: repository.name, UNIDESK_PAC_REPOSITORY_URL: repository.url, UNIDESK_PAC_SECRET_NAME: repository.secretName, UNIDESK_PAC_TOKEN_KEY: repository.tokenKey, UNIDESK_PAC_WEBHOOK_SECRET_KEY: repository.webhookSecretKey, UNIDESK_PAC_CONCURRENCY_LIMIT: String(repository.concurrencyLimit), UNIDESK_PAC_REPOSITORY_PARAMS_JSON: JSON.stringify(repository.params), UNIDESK_PAC_PARAMS_JSON: JSON.stringify(consumerParams(repository, consumer)), UNIDESK_PAC_REGISTRY_APPLICABILITY: registryApplicability(consumer, repository), UNIDESK_PAC_PIPELINE_NAME: consumer.pipeline, UNIDESK_PAC_PIPELINE_RUN_PREFIX: consumer.pipelineRunPrefix, UNIDESK_PAC_CONSUMER_CONFIG_B64: Buffer.from(JSON.stringify(consumerConfig), "utf8").toString("base64"), UNIDESK_PAC_DELIVERY_PROVENANCE_REQUIRED: consumer.deliveryProvenance?.required === true ? "1" : "0", UNIDESK_PAC_CONSUMER_RBAC_MANIFEST_B64: Buffer.from(renderPacConsumerRbacDesiredFragment(target.id), "utf8").toString("base64"), UNIDESK_PAC_RUNNER_SERVICE_ACCOUNT_MANIFEST_B64: Buffer.from(runnerServiceAccountManifest(consumer), "utf8").toString("base64"), UNIDESK_PAC_RUNNER_SERVICE_ACCOUNT_NAME: consumer.runnerServiceAccount?.name ?? "", UNIDESK_PAC_RUNNER_ROLE_BINDING_NAME: consumer.runnerServiceAccount?.roleBindingName ?? "", UNIDESK_PAC_CONSUMER_RBAC_CONFIG_SHA256: rbacIdentity.configSha256, UNIDESK_PAC_ADMISSION_POLICY_NAME: admissionIdentity.policyName, UNIDESK_PAC_ADMISSION_BINDING_NAME: admissionIdentity.bindingName, UNIDESK_PAC_ADMISSION_VERSION: admissionIdentity.version, UNIDESK_PAC_ADMISSION_CONTROLLER_IDENTITY: admissionIdentity.controllerIdentity, UNIDESK_PAC_ADMISSION_CONFIG_SHA256: admissionIdentity.configSha256, UNIDESK_PAC_GITOPS_EXPECTED_REPO_URL: consumer.deliveryProvenance?.gitOps.repoUrl ?? "", UNIDESK_PAC_GITOPS_EXPECTED_TARGET_REVISION: consumer.deliveryProvenance?.gitOps.targetRevision ?? "", UNIDESK_PAC_HISTORY_LIMIT: "limit" in options ? String(options.limit) : "5", UNIDESK_PAC_HISTORY_ID: "detailId" in options && options.detailId !== null ? options.detailId : "", UNIDESK_PAC_HISTORY_CONSUMERS_B64: Buffer.from(JSON.stringify(historyConsumers.map((item) => historyConsumerConfig(pac, item))), "utf8").toString("base64"), UNIDESK_PAC_DISPLAY_TIME_ZONE: pac.display.timeZone, UNIDESK_PAC_ARGO_NAMESPACE: consumer.argoNamespace, UNIDESK_PAC_ARGO_APPLICATION: consumer.argoApplication, UNIDESK_PAC_ARGO_BOOTSTRAP_MANIFEST_B64: Buffer.from(argoBootstrapManifest(consumer), "utf8").toString("base64"), UNIDESK_PAC_ARGO_REPOSITORY_SECRET_NAME: consumer.argoBootstrap?.repositoryCredential?.secretName ?? "", UNIDESK_PAC_ARGO_REPOSITORY_USERNAME: consumer.argoBootstrap?.repositoryCredential?.username ?? "", UNIDESK_PAC_ARGO_REPOSITORY_URL: consumer.argoBootstrap?.repoUrl ?? "", UNIDESK_PAC_PART_OF: pacPartOf(consumer.id), UNIDESK_PAC_SPEC: kubernetesLabelValue(pac.metadata.relatedIssues.includes(1555) ? "GH-1552-GH-1555" : pac.metadata.spec), }; const exports = Object.entries(env).map(([key, value]) => `export ${key}=${shQuote(value)}`).join("\n"); return `${exports}\n${readFileSync(remoteScriptFile, "utf8")}`; } function registryApplicability(consumer: PacConsumer, repository: PacRepository): "configured" | "not-configured" { const params = consumerParams(repository, consumer); const prefix = consumer.id === "unidesk-host" ? "unidesk_host_" : ""; const imageRepository = params[`${prefix}image_repository`] ?? params.image_repository; if (typeof imageRepository !== "string" || imageRepository.length === 0) return "not-configured"; if (prefix.length > 0) return "configured"; return params.pipeline_name === consumer.pipeline ? "configured" : "not-configured"; } function historyConsumerConfig(pac: PacConfig, consumer: PacConsumer): Record { const repository = resolveRepository(pac, consumer.repositoryRef); const admissionIdentity = pacAdmissionDesiredIdentity(consumer.node); const rbacIdentity = pacConsumerRbacDesiredIdentity(consumer.node); return { id: consumer.id, node: consumer.node, namespace: consumer.namespace, pipeline: consumer.pipeline, pipelineRunPrefix: consumer.pipelineRunPrefix, repository: repository.name, repo: `${repository.owner}/${repository.repo}`, repoUrl: repository.url, deliveryProvenance: consumer.deliveryProvenance === null ? null : { required: true, targetId: consumer.node, version: admissionIdentity.version, controllerIdentity: admissionIdentity.controllerIdentity, policyName: admissionIdentity.policyName, bindingName: admissionIdentity.bindingName, configSha256: admissionIdentity.configSha256, rbacConfigSha256: rbacIdentity.configSha256, markerAnnotation: pac.deliveryProvenance.markerAnnotation, markerValue: consumer.deliveryProvenance.markerValue, executionServiceAccountName: consumer.deliveryProvenance.executionServiceAccountName, gitOps: consumer.deliveryProvenance.gitOps, }, }; } function credentialPath(root: string, sourceRef: string): string { return sourceRef.startsWith("/") ? sourceRef : join(root, sourceRef); } function pacPartOf(consumerId: string): string { if (consumerId.startsWith("platform-infra-")) return "platform-infra"; if (consumerId === "unidesk-host") return "unidesk-host"; if (consumerId.startsWith("sentinel")) return "hwlab-web-probe-sentinel"; if (consumerId.startsWith("hwlab-")) return "hwlab"; if (consumerId.startsWith("selfmedia-")) return "selfmedia-factory"; return "agentrun"; } function runnerServiceAccountManifest(consumer: PacConsumer): string { const runner = consumer.runnerServiceAccount; if (runner === null) return ""; const labels = { "app.kubernetes.io/managed-by": "unidesk", "app.kubernetes.io/part-of": pacPartOf(consumer.id), "unidesk.ai/pac-runner": consumer.id, }; const objects = [ { apiVersion: "v1", kind: "ServiceAccount", metadata: { name: runner.name, namespace: consumer.namespace, labels }, automountServiceAccountToken: runner.automountServiceAccountToken, }, { apiVersion: "rbac.authorization.k8s.io/v1", kind: "RoleBinding", metadata: { name: runner.roleBindingName, namespace: consumer.namespace, labels }, subjects: [{ kind: "ServiceAccount", name: runner.name, namespace: consumer.namespace }], roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "Role", name: "unidesk-pac-consumer-runner" }, }, ]; return `${objects.map((item) => Bun.YAML.stringify(item).trim()).join("\n---\n")}\n`; } function argoBootstrapManifest(consumer: PacConsumer): string { const bootstrap = consumer.argoBootstrap; if (bootstrap === null) return ""; const application = { apiVersion: "argoproj.io/v1alpha1", kind: "Application", metadata: { name: consumer.argoApplication, namespace: consumer.argoNamespace, labels: { "app.kubernetes.io/managed-by": "unidesk", "app.kubernetes.io/part-of": pacPartOf(consumer.id), }, }, spec: { project: bootstrap.project, source: { repoURL: bootstrap.repoUrl, targetRevision: bootstrap.targetRevision, path: bootstrap.path, }, destination: { server: "https://kubernetes.default.svc", namespace: bootstrap.destinationNamespace, }, syncPolicy: bootstrap.automated ? { automated: { prune: true, selfHeal: true }, syncOptions: ["CreateNamespace=true"], } : undefined, }, }; return `${Bun.YAML.stringify(application).trim()}\n`; } function parseLinePair(path: string, usernameLine: number, passwordLine: number): { username: string; password: string } { const lines = readFileSync(path, "utf8").split(/\r?\n/u); const username = lines[usernameLine - 1]?.trim() ?? ""; const password = lines[passwordLine - 1]?.trim() ?? ""; if (username.length === 0 || password.length === 0) throw new Error(`${path} must contain username on line ${usernameLine} and password on line ${passwordLine}`); return { username, password }; } function emptySecretMaterial(): SecretMaterial { return { adminUsername: "", adminPassword: "", adminFingerprint: "", webhookSecret: "", webhookFingerprint: "", webhookPath: "", }; } function ensureSecrets(pac: PacConfig, createWebhookSecret: boolean, allowMissingWebhookSecret = false): SecretMaterial { const adminPath = credentialPath(pac.gitea.admin.sourceRoot, pac.gitea.admin.sourceRef); if (!existsSync(adminPath)) throw new Error(`${pac.gitea.admin.sourceRef} is missing`); const admin = parseLinePair(adminPath, pac.gitea.admin.usernameLine, pac.gitea.admin.passwordLine); const webhookPath = credentialPath(pac.gitea.webhook.secretRoot, pac.gitea.webhook.secretSourceRef); if (!existsSync(webhookPath)) { if (allowMissingWebhookSecret) { const placeholder = "dry-run-webhook-secret-placeholder"; return { adminUsername: admin.username, adminPassword: admin.password, adminFingerprint: secretFingerprint(`${admin.username}\0${admin.password}`), webhookSecret: placeholder, webhookFingerprint: secretFingerprint(placeholder), webhookPath, }; } if (!createWebhookSecret) throw new Error(`${pac.gitea.webhook.secretSourceRef} is missing; run apply --confirm to create it`); mkdirSync(dirname(webhookPath), { recursive: true }); writeFileSync(webhookPath, `${randomBytes(32).toString("hex")}\n`, { encoding: "utf8", mode: 0o600 }); chmodSync(webhookPath, 0o600); } const webhookSecret = readFileSync(webhookPath, "utf8").trim(); if (webhookSecret.length < 32) throw new Error(`${pac.gitea.webhook.secretSourceRef} must contain at least 32 chars`); return { adminUsername: admin.username, adminPassword: admin.password, adminFingerprint: secretFingerprint(`${admin.username}\0${admin.password}`), webhookSecret, webhookFingerprint: secretFingerprint(webhookSecret), webhookPath, }; } function secretSummaries(pac: PacConfig): Array> { const adminPath = credentialPath(pac.gitea.admin.sourceRoot, pac.gitea.admin.sourceRef); const webhookPath = credentialPath(pac.gitea.webhook.secretRoot, pac.gitea.webhook.secretSourceRef); const admin = existsSync(adminPath) ? parseLinePair(adminPath, pac.gitea.admin.usernameLine, pac.gitea.admin.passwordLine) : null; const webhookSecret = existsSync(webhookPath) ? readFileSync(webhookPath, "utf8").trim() : ""; return [ { id: "gitea-admin", sourceRef: pac.gitea.admin.sourceRef, sourcePath: adminPath, present: admin !== null, fingerprint: admin === null ? null : secretFingerprint(`${admin.username}\0${admin.password}`), valuesPrinted: false, }, { id: "pac-webhook", sourceRef: pac.gitea.webhook.secretSourceRef, sourcePath: webhookPath, present: webhookSecret.length > 0, fingerprint: webhookSecret.length > 0 ? secretFingerprint(webhookSecret) : null, valuesPrinted: false, }, ]; } function statusSummary(payload: Record): Record { const pipelineRuns = arrayRecords(payload.pipelineRuns); const latest = pipelineRuns[0] ?? {}; const taskRuns = arrayRecords(payload.taskRuns); const argo = record(payload.argo); const runtime = record(payload.runtime); const diagnostics = record(payload.diagnostics); const admissionProvenance = record(payload.admissionProvenance); const consumerBootstrap = record(payload.consumerBootstrap); const webhooks = arrayRecords(payload.webhooks); const rawArtifact = record(payload.artifact); const sourceObservation = record(rawArtifact.sourceObservation); const provenance = record(diagnostics.provenance); const noRuntimeChange = stringValue(sourceObservation.mode) === "no-runtime-change" && sourceObservation.valid === true && stringValue(diagnostics.code) === "pac-ready-no-runtime-change"; const artifact = { ...rawArtifact, imageStatus: noRuntimeChange ? "retained" : rawArtifact.imageStatus ?? (runtime.image === undefined ? undefined : "runtime"), digest: noRuntimeChange ? provenance.runtimeDigest ?? runtime.digest : rawArtifact.digest ?? runtime.digest, gitopsCommit: noRuntimeChange ? provenance.gitopsRevision ?? argo.revision : rawArtifact.gitopsCommit ?? argo.revision, provenanceRelation: noRuntimeChange ? "retained" : record(argo.revisionRelation).relation, }; const pipelineGate = pipelineRunGate(latest); return { ready: payload.crdPresent === true && String(payload.controllerReady ?? "0/0") !== "0/0" && pipelineGate.ok && diagnostics.ok !== false && consumerBootstrap.ready !== false, crdPresent: payload.crdPresent === true, controllerReady: payload.controllerReady, repositoryCondition: payload.repositoryCondition, repository: record(payload.repository), webhooks, webhookCount: webhooks.length, latestPipelineRun: latest, taskRuns, pipelineRunGate: pipelineGate, artifact, sourceObservation: Object.keys(sourceObservation).length === 0 ? null : sourceObservation, provenance: Object.keys(provenance).length === 0 ? null : provenance, argo, runtime, diagnostics, admissionProvenance, consumerBootstrap, valuesPrinted: false, }; } function nodeStatusRow(targetId: string, consumer: PacConsumer, repository: PacRepository, current: Record): Record { const summary = record(current.summary); const latest = record(summary.latestPipelineRun); const artifact = record(summary.artifact); const argo = record(summary.argo); const runtime = record(summary.runtime); const diagnostics = record(summary.diagnostics); const pipelineGate = record(summary.pipelineRunGate); const deliveryDisabled = stringValue(artifact.imageStatus) === "disabled"; const desired = numericValue(runtime.replicas); const readyReplicas = numericValue(runtime.readyReplicas); const runtimeReady = deliveryDisabled || (desired !== null && desired > 0 && readyReplicas === desired); const argoReady = deliveryDisabled || (stringValue(argo.sync) === "Synced" && stringValue(argo.health) === "Healthy"); const ciReady = pipelineGate.ok === true; const diagnosticsReady = diagnostics.ok !== false; const ready = current.ok === true && ciReady && argoReady && runtimeReady && diagnosticsReady; return { consumer: consumer.id, node: consumer.node, lane: consumer.lane, repository: `${repository.owner}/${repository.repo}`, namespace: consumer.namespace, pipeline: consumer.pipeline, argoApplication: consumer.argoApplication, ready, ciReady, argoReady, runtimeReady, diagnosticsReady, latestPipelineRun: stringValue(latest.name), pipelineStatus: statusText(latest), durationSeconds: latest.durationSeconds ?? null, sourceCommit: stringValue(latest.sourceCommit), imageStatus: stringValue(artifact.imageStatus), envReuse: envReuseText(record(artifact.envReuse)), digest: stringValue(artifact.digest), gitopsCommit: stringValue(artifact.gitopsCommit), argo: { sync: stringValue(argo.sync), health: stringValue(argo.health), revision: stringValue(argo.revision), repoURL: stringValue(argo.repoURL), targetRevision: stringValue(argo.targetRevision), revisionRelation: record(argo.revisionRelation), }, runtime: { deployment: stringValue(runtime.deployment), readyReplicas: runtime.readyReplicas ?? null, replicas: runtime.replicas ?? null, digest: stringValue(runtime.digest), image: stringValue(runtime.image), }, diagnostics: { ok: diagnostics.ok !== false, code: stringValue(diagnostics.code), phase: stringValue(diagnostics.phase), hint: compactLine(stringValue(diagnostics.hint)), registry: record(diagnostics.registry), }, traceId: stringValue(artifact.traceId), firstBreak: record(artifact.firstBreak), error: record(artifact.error), reason: ready ? "ready" : nodeStatusReason({ ciReady, argoReady, runtimeReady, diagnosticsReady, pipelineStatus: statusText(latest), diagnostics }), statusCommand: `bun scripts/cli.ts platform-infra pipelines-as-code status --target ${targetId} --consumer ${consumer.id}`, historyCommand: `bun scripts/cli.ts platform-infra pipelines-as-code history --target ${targetId} --consumer ${consumer.id} --limit 10`, valuesPrinted: false, }; } function nodeStatusReason(input: { ciReady: boolean; argoReady: boolean; runtimeReady: boolean; diagnosticsReady: boolean; pipelineStatus: string; diagnostics: Record }): string { if (!input.ciReady) return `ci:${input.pipelineStatus}`; if (!input.argoReady) return "argo-not-ready"; if (!input.runtimeReady) return "runtime-not-ready"; if (!input.diagnosticsReady) return stringValue(input.diagnostics.code, "diagnostics-not-ready"); return "not-ready"; } function policyChecks(repository: PacRepository): Array> { return [ { name: "single-path", ok: true, detail: "Gitea webhook -> Pipelines-as-Code -> Tekton -> Argo/k8s runtime; no Gitea Actions/act_runner/branch-follower fallback." }, { name: "yaml-source-of-truth", ok: true, detail: `${configLabel} owns PaC release, Repository CR, Gitea webhook and consumer params.` }, { name: "public-repository-url", ok: !isInternalRepositoryMatchUrl(repository.url), detail: "Repository CR url matches the public Gitea webhook payload URL; internal service URLs remain clone/read URLs only." }, { name: "gitea-internal-source", ok: repository.cloneUrl.includes(".svc.cluster.local"), detail: "Tekton source clone uses the internal k3s Gitea service URL." }, { name: "runtime-zero-docker", ok: true, detail: "Runtime starts at k8s pulling already-built images; CI build may use Tekton/BuildKit." }, ]; } function isInternalRepositoryMatchUrl(value: string): boolean { const parsed = new URL(value); return parsed.hostname.endsWith(".svc.cluster.local") || parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || /^10\./u.test(parsed.hostname) || /^192\.168\./u.test(parsed.hostname) || /^172\.(1[6-9]|2\d|3[0-1])\./u.test(parsed.hostname); } function targetSummary(target: PacTarget): Record { return { id: target.id, route: target.route, namespace: target.namespace, role: target.role }; } function configSummary(pac: PacConfig, consumer: PacConsumer, repository: PacRepository): Record { return { path: configLabel, metadata: pac.metadata, release: pac.release, capabilities: capabilitySummary(pac), deliveryTiming: pac.deliveryTiming, gitea: { configRef: pac.gitea.configRef, internalBaseUrl: pac.gitea.internalBaseUrl, webhookBranch: pac.gitea.webhook.branch }, display: pac.display, repositories: pac.repositories.map(repositorySummary), consumers: pac.consumers.map(consumerObservationSummary), repository: repositorySummary(repository), consumer, valuesPrinted: false, }; } function compactConfigSummary(pac: PacConfig, consumer: PacConsumer, repository: PacRepository): Record { return { path: configLabel, releaseVersion: pac.release.version, capabilities: capabilitySummary(pac), repository: repository.name, providerType: repository.providerType, sourceUrl: repository.cloneUrl, displayTimeZone: pac.display.timeZone, deliveryTiming: pac.deliveryTiming, consumer: `${consumer.node}/${consumer.lane}`, consumerId: consumer.id, pipeline: consumer.pipeline, valuesPrinted: false, }; } function capabilitySummary(pac: PacConfig): Record { return { sentinelInternalPublish: { ...pac.capabilities.sentinelInternalPublish, configRef: `${configLabel}#capabilities.sentinelInternalPublish`, valuesPrinted: false, }, valuesPrinted: false, }; } function repositorySummary(repository: PacRepository): Record { return { id: repository.id, name: repository.name, namespace: repository.namespace, providerType: repository.providerType, url: repository.url, cloneUrl: repository.cloneUrl, owner: repository.owner, repo: repository.repo, secretName: repository.secretName, concurrencyLimit: repository.concurrencyLimit, params: Object.keys(repository.params).sort(), }; } function consumerObservationSummary(consumer: PacConsumer): Record { return { id: consumer.id, node: consumer.node, lane: consumer.lane, namespace: consumer.namespace, pipeline: consumer.pipeline, pipelineRunPrefix: consumer.pipelineRunPrefix, argoNamespace: consumer.argoNamespace, argoApplication: consumer.argoApplication, repositoryRef: consumer.repositoryRef, params: Object.keys(consumer.params).sort(), runnerServiceAccount: consumer.runnerServiceAccount, argoRepositoryCredential: consumer.argoBootstrap?.repositoryCredential === null || consumer.argoBootstrap?.repositoryCredential === undefined ? null : { secretName: consumer.argoBootstrap.repositoryCredential.secretName, usernameConfigured: consumer.argoBootstrap.repositoryCredential.username.length > 0, }, valuesPrinted: false, }; } function consumerCoverage(pac: PacConfig, targetId: string): Record[] { return validPacConsumers(pac).filter((consumer) => consumer.node.toLowerCase() === targetId.toLowerCase()).map((consumer) => { const repository = resolveRepository(pac, consumer.repositoryRef); return { consumer: consumer.id, source: `${repository.owner}/${repository.repo}@${consumerParams(repository, consumer).source_branch ?? "-"}`, namespace: consumer.namespace, pipeline: consumer.pipeline, argoApplication: consumer.argoApplication, statusCommand: `bun scripts/cli.ts platform-infra pipelines-as-code status --target ${targetId} --consumer ${consumer.id}`, historyCommand: `bun scripts/cli.ts platform-infra pipelines-as-code history --target ${targetId} --consumer ${consumer.id}`, }; }); } function nextCommands(targetId: string, consumerId: string, _defaultConsumerId: string): Record { return pacReadOnlyNext(targetId, consumerId); } function compactPlanJson(result: Record): Record { const config = record(result.config); const consumer = record(result.consumer); return { ok: result.ok === true, action: result.action, mutation: false, target: result.target, config: { path: config.path, metadata: config.metadata, release: config.release, capabilities: config.capabilities, gitea: config.gitea, display: config.display, deliveryTiming: config.deliveryTiming, valuesPrinted: false, }, repository: result.repository, consumer: { id: consumer.id, node: consumer.node, lane: consumer.lane, namespace: consumer.namespace, pipeline: consumer.pipeline, pipelineRunPrefix: consumer.pipelineRunPrefix, argoNamespace: consumer.argoNamespace, argoApplication: consumer.argoApplication, repositoryRef: consumer.repositoryRef, params: consumer.params, runnerServiceAccount: consumer.runnerServiceAccount, argoRepositoryCredential: consumer.argoRepositoryCredential, }, secrets: result.secrets, policy: result.policy, warnings: result.warnings, next: result.next, valuesPrinted: false, }; } function compactStatusJson(result: Record): Record { const consumer = record(result.consumer); return { ok: result.ok === true, action: result.action, mutation: false, target: result.target, consumer: { id: consumer.id, node: consumer.node, lane: consumer.lane, namespace: consumer.namespace, pipeline: consumer.pipeline, argoApplication: consumer.argoApplication, }, deliveryAuthority: result.deliveryAuthority, capabilities: record(result.config).capabilities, warnings: result.warnings, deliveryBudget: result.deliveryBudget, summary: compactStatusSummary(record(result.summary)), next: result.next, valuesPrinted: false, }; } export function compactStatusSummary(summary: Record): Record { const latest = record(summary.latestPipelineRun); const artifact = record(summary.artifact); const argo = record(summary.argo); const runtime = record(summary.runtime); const diagnostics = record(summary.diagnostics); const taskRuns = arrayRecords(summary.taskRuns); const longest = longestTaskRun(taskRuns); return { ready: summary.ready === true, crdPresent: summary.crdPresent === true, controllerReady: summary.controllerReady, repositoryCondition: summary.repositoryCondition, webhookCount: summary.webhookCount, latestPipelineRun: { name: latest.name, status: latest.status, reason: latest.reason, durationSeconds: latest.durationSeconds, sourceCommit: latest.sourceCommit, }, taskRuns: { count: taskRuns.length, longest: longest === null ? null : { name: longest.name, status: longest.status, reason: longest.reason, durationSeconds: longest.durationSeconds, }, }, pipelineRunGate: summary.pipelineRunGate, admissionProvenance: summary.admissionProvenance, consumerBootstrap: summary.consumerBootstrap, sourceObservation: summary.sourceObservation, artifact: { imageStatus: artifact.imageStatus, envReuse: artifact.envReuse, envIdentity: artifact.envIdentity, digest: artifact.digest, gitopsCommit: artifact.gitopsCommit, provenanceRelation: artifact.provenanceRelation, traceId: artifact.traceId ?? latest.traceId, firstBreak: artifact.firstBreak ?? latest.firstBreak, error: artifact.error ?? latest.error, }, argo: { sync: argo.sync, health: argo.health, revision: argo.revision, repoURL: argo.repoURL, targetRevision: argo.targetRevision, revisionRelation: argo.revisionRelation, }, runtime: { deployment: runtime.deployment, readyReplicas: runtime.readyReplicas, replicas: runtime.replicas, image: runtime.image, digest: runtime.digest, }, diagnostics: { ok: diagnostics.ok !== false, code: diagnostics.code, phase: diagnostics.phase, hint: diagnostics.hint, deliveryMode: diagnostics.deliveryMode, registry: diagnostics.registry, }, provenance: summary.provenance, valuesPrinted: false, }; } function compactCloseoutJson(result: Record): Record { const consumer = record(result.consumer); const flush = record(result.gitOpsMirrorFlush); return { ok: result.ok === true, action: result.action, mutation: result.mutation === true, target: result.target, consumer: { id: consumer.id, node: consumer.node, lane: consumer.lane }, sourceCommit: result.sourceCommit, observedSourceCommit: result.observedSourceCommit, sourceMatched: result.sourceMatched === true, ready: result.ready === true, timedOut: result.timedOut === true, elapsedMs: result.elapsedMs, cursor: result.cursor, wait: result.wait, summary: compactStatusSummary(record(result.summary)), gitOpsMirrorFlush: { ok: flush.ok !== false, enabled: flush.enabled, required: flush.required, applicable: flush.applicable, mode: flush.mode, executed: flush.executed === true, reason: flush.reason, }, blocker: result.blocker, next: result.next, valuesPrinted: false, }; } export function compactHistoryJson(result: Record, includeTaskDetails = false): Record { const detailRequested = stringValue(result.detailId) !== "-"; return { ok: result.ok === true, action: result.action, mutation: false, target: result.target, config: result.config, consumers: result.consumers, detailId: result.detailId, deliveryBudget: result.deliveryBudget, rows: arrayRecords(result.rows).map((row) => { const taskRuns = record(row.taskRuns); const artifact = record(row.artifact); const catalog = record(artifact.catalog); const collector = record(row.collector); return { id: row.id ?? row.pipelineRun, consumer: row.consumer, repo: row.repo, pipeline: row.pipeline, triggeredAt: row.triggeredAt, startTime: row.startTime, completionTime: row.completionTime, displayTime: row.displayTime, displayTimeZone: row.displayTimeZone, durationSeconds: row.durationSeconds, status: row.status, reason: row.reason, commit: row.commit, branch: row.branch, deliveryClass: row.deliveryClass, deliveryAuthorityEligible: row.deliveryAuthorityEligible === true, deliveryOwner: row.deliveryOwner, parentRelation: row.parentRelation, classification: row.classification, envReuse: row.envReuse, sourceObservation: row.sourceObservation, traceId: row.traceId, firstBreak: row.firstBreak, error: row.error, artifact: detailRequested ? { imageStatus: artifact.imageStatus, envReuse: artifact.envReuse, digest: artifact.digest, digests: artifact.digests, gitopsCommit: artifact.gitopsCommit, sourceCommit: artifact.sourceCommit, action: artifact.action, reason: artifact.reason, catalog: { present: catalog.present === true, status: catalog.status, sourceCommit: catalog.sourceCommit, registryVerified: catalog.registryVerified === true, publishedCount: catalog.publishedCount, reusedCount: catalog.reusedCount, requiredServiceCount: catalog.requiredServiceCount, digestCount: catalog.digestCount, source: catalog.source, valuesPrinted: false, }, valuesPrinted: false, } : undefined, collector: detailRequested ? { mode: collector.mode, matchingTaskCount: collector.matchingTaskCount, contractTaskCount: collector.contractTaskCount, terminalRecordCount: collector.terminalRecordCount, logErrorCount: collector.logErrorCount, logErrors: collector.logErrors, valuesPrinted: false, } : undefined, taskRuns: { count: taskRuns.count, longest: taskRuns.longest, failed: taskRuns.failed, details: includeTaskDetails ? taskRuns.details : undefined, logsCommand: taskRuns.logsCommand, }, }; }), historyErrors: result.historyErrors, warnings: result.warnings, next: result.next, valuesPrinted: false, }; } function renderPlan(result: Record): RenderedCliResult { const target = record(result.target); const config = record(result.config); const repository = record(result.repository); const consumer = record(result.consumer); const internalPublish = record(record(config.capabilities).sentinelInternalPublish); const secrets = arrayRecords(result.secrets); const policy = arrayRecords(result.policy); const warnings = arrayRecords(result.warnings); const lines = [ "PLATFORM-INFRA PIPELINES-AS-CODE PLAN", ...table(["TARGET", "NAMESPACE", "RELEASE", "REPOSITORY", "CONSUMER"], [[stringValue(target.id), stringValue(repository.namespace), stringValue(record(config.release).version), stringValue(repository.name), stringValue(consumer.id)]]), "", "PATH", ...table(["STAGE", "AUTHORITY", "DETAIL"], [ ["source", "Gitea", stringValue(repository.cloneUrl)], ["trigger", "Pipelines-as-Code", "Gitea push webhook"], ["ci", "Tekton", stringValue(consumer.pipeline)], ["cd", "Argo", stringValue(consumer.argoApplication)], ]), "", "SECRETS", ...table(["ID", "PRESENT", "FINGERPRINT", "VALUES"], secrets.map((item) => [stringValue(item.id), boolText(item.present), stringValue(item.fingerprint), "false"])), "", "POLICY", ...table(["NAME", "OK", "DETAIL"], policy.map((item) => [stringValue(item.name), boolText(item.ok), stringValue(item.detail)])), ...renderPacConfigWarnings(warnings), "", "CAPABILITIES", ...table(["NAME", "ENABLED", "PROVENANCE", "TRACKING"], [[ "sentinelInternalPublish", boolText(internalPublish.enabled), stringValue(internalPublish.admissionProvenance), stringValue(internalPublish.trackingIssue), ]]), "", "READ-ONLY NEXT", ` status: ${stringValue(record(result.next).status)}`, ` history: ${stringValue(record(result.next).history)}`, ]; return rendered(result, "platform-infra pipelines-as-code plan", lines); } function renderApply(result: Record): RenderedCliResult { const target = record(result.target); const remote = record(result.remote); const preflight = record(remote.preflight); const errorText = compactLine(stringValue(remote.stderrTail, stringValue(remote.error, stringValue(result.error, "")))); const warnings = arrayRecords(result.warnings); const lines = [ "PLATFORM-INFRA PIPELINES-AS-CODE APPLY", ...table(["TARGET", "MODE", "MUTATION", "OK"], [[stringValue(target.id), stringValue(result.mode), boolText(result.mutation), boolText(result.ok)]]), "", "REMOTE", ...table(["CONTROLLER", "WEBHOOK", "REPOSITORY", "EXIT", "VALUES"], [[stringValue(remote.controllerReady), stringValue(record(remote.webhook).present), stringValue(remote.repository), stringValue(remote.exitCode, "0"), "false"]]), ...(Object.keys(preflight).length === 0 ? [] : [ "", "PREFLIGHT", ...table(["GITEA_REPOSITORY", "HTTP", "EXISTS"], [[stringValue(preflight.repository), stringValue(preflight.httpStatus), boolText(preflight.repositoryExists)]]), ]), ...(result.ok === false && errorText.length > 0 ? ["", `ERROR ${errorText}`] : []), ...(remote.error === "gitea-repository-missing" ? ["HELP bun scripts/cli.ts platform-infra pipelines-as-code help platform-bootstrap"] : []), ...renderPacConfigWarnings(warnings), "", "NEXT", ` status: ${stringValue(record(result.next).status)}`, ]; return rendered(result, "platform-infra pipelines-as-code apply", lines); } export function renderStatus(result: Record): RenderedCliResult { const summary = record(result.summary); const consumer = record(result.consumer); const deliveryAuthority = record(result.deliveryAuthority); const internalPublish = record(record(record(result.config).capabilities).sentinelInternalPublish); const coverage = arrayRecords(result.coverage); const warnings = arrayRecords(result.warnings); const latest = record(summary.latestPipelineRun); const deliveryBudget = record(result.deliveryBudget); const taskRuns = arrayRecords(summary.taskRuns); const artifact = record(summary.artifact); const argo = record(summary.argo); const diagnostics = record(summary.diagnostics); const latestFirstBreak = record(artifact.firstBreak ?? latest.firstBreak); const latestError = record(artifact.error ?? latest.error); const latestTraceId = stringValue(artifact.traceId ?? latest.traceId); const admissionProvenance = record(summary.admissionProvenance); const consumerBootstrap = record(summary.consumerBootstrap); const bootstrapRunner = record(consumerBootstrap.runner); const bootstrapArgo = record(consumerBootstrap.argoRepository); const sourceObservation = record(summary.sourceObservation); const deliveryPlan = record(sourceObservation.plan); const repository = record(summary.repository); const webhooks = arrayRecords(summary.webhooks); const lines = [ "PLATFORM-INFRA PIPELINES-AS-CODE STATUS", ...renderPacConfigWarnings(warnings), ...(coverage.length === 0 ? [] : [ "COVERAGE", ...table(["CONSUMER", "SOURCE", "NAMESPACE", "PIPELINE", "ARGO_APP"], coverage.map((item) => [stringValue(item.consumer), stringValue(item.source), stringValue(item.namespace), stringValue(item.pipeline), stringValue(item.argoApplication)])), "", ]), ...table(["CONSUMER", "READY", "AUTHORITY", "CRD", "CONTROLLER", "WEBHOOKS", "REPO_URL"], [[stringValue(consumer.id), boolText(summary.ready), stringValue(deliveryAuthority.kind), boolText(summary.crdPresent), stringValue(summary.controllerReady), stringValue(summary.webhookCount), short(stringValue(repository.url), 56)]]), ` repository-condition: ${compactLine(stringValue(summary.repositoryCondition))}`, ` sentinel-internal-publish: enabled=${boolText(internalPublish.enabled)} provenance=${stringValue(internalPublish.admissionProvenance)} tracking=${stringValue(internalPublish.trackingIssue)}`, ...(admissionProvenance.configured === false ? [" admission-provenance: not-required"] : [ ` admission-provenance: ready=${boolText(admissionProvenance.ready)} policy=${stringValue(admissionProvenance.policyName)} binding=${stringValue(admissionProvenance.bindingName)} active-after=${stringValue(admissionProvenance.activeAfter)} default-can-mutate=${boolText(admissionProvenance.defaultCanMutate)}`, ` admission-reasons: ${Array.isArray(admissionProvenance.reasons) ? admissionProvenance.reasons.join(",") || "-" : "-"}`, ]), ...(consumerBootstrap.configured !== true ? [" consumer-bootstrap: not-declared"] : [ ` consumer-bootstrap: ready=${boolText(consumerBootstrap.ready)} runner=${stringValue(bootstrapRunner.serviceAccount)} exists=${boolText(bootstrapRunner.exists)} automount-disabled=${boolText(bootstrapRunner.automountDisabled)} rolebinding-exact=${boolText(bootstrapRunner.roleBindingExact)}`, ` argocd-repository-secret: name=${stringValue(bootstrapArgo.secretName)} exists=${boolText(bootstrapArgo.exists)} label=${boolText(bootstrapArgo.labelReady)} keys=${boolText(bootstrapArgo.keysReady)} url=${boolText(bootstrapArgo.urlReady)} password-present=${boolText(bootstrapArgo.passwordPresent)} password-decoded=${boolText(bootstrapArgo.passwordDecoded)}`, ` consumer-bootstrap-reasons: ${Array.isArray(consumerBootstrap.reasons) ? consumerBootstrap.reasons.join(",") || "-" : "-"}`, ]), "", "GITEA HOOKS", ...(webhooks.length === 0 ? ["-"] : table(["HOOK", "ACTIVE", "EVENTS", "URL"], webhooks.map((item) => [stringValue(item.id), boolText(item.active), Array.isArray(item.events) ? item.events.join(",") : stringValue(item.events), short(stringValue(item.url), 56)]))), "", "LATEST PIPELINERUN", ...table(["NAME", "STATUS", "REASON", "DURATION_S", "SOURCE"], [[stringValue(latest.name), stringValue(latest.status), stringValue(latest.reason), stringValue(latest.durationSeconds), short(stringValue(latest.sourceCommit))]]), ` delivery-budget: state=${stringValue(deliveryBudget.state)} end-to-end=${stringValue(deliveryBudget.observedSeconds)}s/${stringValue(deliveryBudget.budgetSeconds)}s exact=${stringValue(deliveryBudget.exactCommand)}`, "", "DELIVERY OBSERVATION", ...table(["MODE", "VALID", "SOURCE_MATCHED", "AFFECTED", "ROLLOUT", "BUILD", "REUSED", "REASON"], [[ stringValue(sourceObservation.mode), boolText(sourceObservation.valid), boolText(sourceObservation.sourceMatched ?? (stringValue(sourceObservation.sourceCommit) === stringValue(latest.sourceCommit))), stringValue(deliveryPlan.affectedServiceCount), stringValue(deliveryPlan.rolloutServiceCount), stringValue(deliveryPlan.buildServiceCount), stringValue(deliveryPlan.reusedServiceCount), stringValue(sourceObservation.reason), ]]), renderFeatureConfigSchemaLine(sourceObservation.featureConfigSchema), "", "TASKRUN DURATIONS", ...(taskRuns.length === 0 ? ["-"] : table(["TASKRUN", "STATUS", "REASON", "FAILED_STEP", "EXIT", "DURATION_S"], taskRuns.map((item) => { const failedStep = record(item.failedStep); return [short(stringValue(item.name), 48), stringValue(item.status), stringValue(item.reason), stringValue(failedStep.name), stringValue(failedStep.exitCode), stringValue(item.durationSeconds)]; }))), "", "IMAGE / GITOPS", ...table(["IMAGE_STATUS", "ENV_REUSE", "ENV_ID", "DIGEST", "GITOPS"], [[stringValue(artifact.imageStatus), stringValue(artifact.envReuse), stringValue(artifact.envIdentity), short(stringValue(artifact.digest), 18), short(stringValue(artifact.gitopsCommit))]]), ` stage-timings: ${stageTimingsText(record(artifact.stageTimings))}`, "", "ARGO", ...table(["SYNC", "HEALTH", "REVISION", "RELATION", "TARGET_REVISION"], [[ stringValue(argo.sync), stringValue(argo.health), short(stringValue(argo.revision)), stringValue(record(argo.revisionRelation).relation), stringValue(argo.targetRevision), ]]), ` repo-url: ${compactLine(stringValue(argo.repoURL))}`, "", "CICD DIAGNOSIS", ...table(["OK", "CODE", "PHASE", "TRACE", "FIRST_BREAK", "ERROR", "EXIT", "SOURCE", "REGISTRY", "GITOPS", "RUNTIME"], [[ boolText(diagnostics.ok !== false), stringValue(diagnostics.code), stringValue(latestFirstBreak.phase ?? latestError.phase ?? diagnostics.phase), short(latestTraceId, 16), stringValue(latestFirstBreak.code), `${stringValue(latestError.type)}/${stringValue(latestError.code)}`, stringValue(latestError.exitCode), short(stringValue(diagnostics.sourceCommit)), registryText(record(diagnostics.registry)), short(stringValue(record(diagnostics.gitops).commit)), runtimeText(record(diagnostics.runtime)), ]]), ` hint: ${compactLine(stringValue(diagnostics.hint))}`, ` pipeline-run: ${latest.name === undefined ? "-" : `bun scripts/cli.ts platform-infra pipelines-as-code history --target ${stringValue(record(result.target).id)} --id ${stringValue(latest.name)}`}`, "", "NEXT", ` full: ${stringValue(record(result.next).status)} --full`, ` all-history: ${stringValue(record(result.next).history).replace(/ --consumer [^ ]+/u, "")} --limit 10`, ` fix-automatic-delivery: ${stringValue(record(record(result.next).fixAutomaticDelivery).reference)}`, ]; return rendered(result, "platform-infra pipelines-as-code status", lines); } function renderPacConfigWarnings(warnings: readonly Record[]): string[] { if (warnings.length === 0) return []; return [ "", "NON-BLOCKING WARNINGS", ...table(["OBJECT", "BLOCKING", "CODE", "CONFIG", "OBSERVED/BUDGET"], warnings.map((item) => [ `${stringValue(record(item.object).kind)}:${stringValue(record(item.object).id)}`, boolText(item.blocking), stringValue(item.code), stringValue(item.configPath), item.observedSeconds === undefined ? "-" : `${stringValue(item.observedSeconds)}s/${stringValue(item.budgetSeconds)}s`, ])), ...warnings.flatMap((item) => { const hint = record(item.hint); return Object.keys(hint).length === 0 ? [] : [ ` hint: ${compactLine(stringValue(hint.summary))}`, ` status: ${stringValue(hint.status)}`, ` history: ${stringValue(hint.history)}`, ]; }), ]; } function renderCloseout(result: Record): RenderedCliResult { const target = record(result.target); const consumer = record(result.consumer); const wait = record(result.wait); const summary = record(result.summary); const latest = record(summary.latestPipelineRun); const artifact = record(summary.artifact); const argo = record(summary.argo); const runtime = record(summary.runtime); const diagnostics = record(summary.diagnostics); const sourceObservation = record(summary.sourceObservation); const blocker = record(result.blocker); const gitOpsMirrorFlush = record(result.gitOpsMirrorFlush); const lines = [ "PLATFORM-INFRA PIPELINES-AS-CODE CLOSEOUT", ...table(["TARGET", "CONSUMER", "READY", "SOURCE_MATCHED", "MUTATION"], [[stringValue(target.id), stringValue(consumer.id), boolText(result.ready), boolText(result.sourceMatched), boolText(result.mutation)]]), "", "WAIT", ...table(["REQUESTED", "COMPLETED", "TIMED_OUT", "CANCELLED", "OBSERVATIONS", "ELAPSED_MS", "BUDGET_S"], [[ stringValue(wait.requested), boolText(wait.completed), boolText(wait.timedOut), boolText(wait.cancelled), stringValue(wait.observations), stringValue(wait.elapsedMs), stringValue(wait.budgetSeconds), ]]), ` identity: ${compactLine(JSON.stringify(wait.identity ?? {}))}`, ` cursor: ${stringValue(wait.cursor)}`, "", "SOURCE / PIPELINERUN", ...table(["EXPECTED", "OBSERVED", "PIPELINERUN", "STATUS", "DURATION_S"], [[short(stringValue(result.sourceCommit), 16), short(stringValue(result.observedSourceCommit), 16), stringValue(latest.name), stringValue(latest.reason ?? latest.status), stringValue(latest.durationSeconds)]]), ` delivery: ${stringValue(sourceObservation.mode)} (${stringValue(sourceObservation.reason)})`, "", "RUNTIME", ...table(["IMAGE_STATUS", "ENV_REUSE", "DIGEST", "GITOPS", "ARGO", "RELATION", "RUNTIME"], [[ stringValue(artifact.imageStatus), stringValue(artifact.envReuse), short(stringValue(artifact.digest), 18), short(stringValue(artifact.gitopsCommit), 12), `${stringValue(argo.sync)}/${stringValue(argo.health)}`, stringValue(record(argo.revisionRelation).relation), runtimeText(runtime), ]]), "", "CICD DIAGNOSIS", ...table(["OK", "CODE", "PHASE", "HINT"], [[boolText(diagnostics.ok !== false), stringValue(diagnostics.code), stringValue(diagnostics.phase), compactLine(stringValue(diagnostics.hint))]]), "", "AUTOMATIC DELIVERY OBSERVATION", ...table(["ENABLED", "REQUIRED", "OK", "MODE", "EXECUTED", "PENDING", "IN_SYNC"], [[ boolText(gitOpsMirrorFlush.enabled), boolText(gitOpsMirrorFlush.required), boolText(gitOpsMirrorFlush.ok !== false), stringValue(gitOpsMirrorFlush.mode), boolText(gitOpsMirrorFlush.executed), stringValue(record(gitOpsMirrorFlush.afterSummary ?? gitOpsMirrorFlush.beforeSummary).pendingFlush), stringValue(record(gitOpsMirrorFlush.afterSummary ?? gitOpsMirrorFlush.beforeSummary).githubInSync), ]]), "", ...(Object.keys(blocker).length === 0 ? ["BLOCKER", "-"] : ["BLOCKER", ...table(["CODE", "REASON"], [[stringValue(blocker.code), compactLine(stringValue(blocker.reason))]])]), "", "NEXT", ` status: ${stringValue(record(result.next).status)}`, ` history: ${stringValue(record(result.next).history)} --limit 10`, ` wait: ${stringValue(record(result.next).wait)}`, latest.name === undefined ? " pipeline-run: -" : ` pipeline-run: ${stringValue(record(result.next).history)} --id ${stringValue(latest.name)}`, ]; return rendered(result, "platform-infra pipelines-as-code closeout", lines); } async function deliveryTiming(config: UniDeskConfig, options: CommonOptions): Promise> { const pac = readPacConfig({ consumerId: options.consumerId, selectDefault: true }); const target = resolveTarget(pac, options.targetId); const consumer = resolveConsumer(pac, options.consumerId); if (consumer.node.toLowerCase() !== target.id.toLowerCase()) throw new Error(`Pipelines-as-Code consumer ${consumer.id} belongs to ${consumer.node}, not target ${target.id}`); const authority = resolveCicdDeliveryAuthority({ consumerId: consumer.id, node: consumer.node, lane: consumer.lane }); if (authority.kind !== "pac-pr-merge") throw new Error(`delivery authority is ${authority.kind}: ${authority.reason}`); const historyOptions: HistoryOptions = { ...options, limit: 1, detailId: null }; return await observePacDeliveryTiming({ target: targetSummary(target), consumer: { id: consumer.id, node: consumer.node, lane: consumer.lane, repository: authority.consumer.sourceRepository, branch: authority.consumer.sourceBranch, pipeline: consumer.pipeline, }, policy: pac.deliveryTiming, }, { history: async () => await history(config, historyOptions), status: async () => await status(config, options), }); } function renderDeliveryTiming(result: Record): RenderedCliResult { const timing = record(result.timing); const source = record(result.source); const pr = record(source.pullRequest); const runtime = record(result.runtime); const argo = record(runtime.argo); const health = record(runtime.health); const deliveryBudget = record(result.deliveryBudget); const warnings = arrayRecords(result.warnings); const lines = [ "PLATFORM-INFRA DELIVERY TIMING", `STATE: ${stringValue(result.state)} MUTATION: ${boolText(result.mutation)}`, `SOURCE: ${stringValue(record(result.consumer).repository)}@${stringValue(source.commit)} PR#${stringValue(pr.number)}`, `MERGED_AT: ${stringValue(timing.mergedAt)} PIPELINE_START: ${stringValue(timing.pipelineStart)} PIPELINE_DONE: ${stringValue(timing.pipelineCompletion)}`, `TRIGGER_WAIT_S: ${stringValue(timing.triggerWaitSeconds)} PIPELINE_S: ${stringValue(timing.pipelineDurationSeconds)} END_TO_END_S: ${stringValue(timing.endToEndSeconds)}`, `BUDGET_S: ${stringValue(deliveryBudget.budgetSeconds)} OVER_BUDGET: ${stringValue(deliveryBudget.overBudget)} BUDGET_STATE: ${stringValue(deliveryBudget.state)}`, `ARGO: ${stringValue(argo.sync)}/${stringValue(argo.health)} RUNTIME_READY: ${stringValue(health.readyReplicas)}/${stringValue(health.replicas)}`, `EVIDENCE_GAPS: ${(Array.isArray(result.evidenceGaps) ? result.evidenceGaps.map(String) : []).join(", ") || "-"}`, ...renderPacConfigWarnings(warnings), ]; return rendered(result, "platform-infra pipelines-as-code delivery-timing", lines); } export function renderHistory(result: Record): RenderedCliResult { const rows = arrayRecords(result.rows); const config = record(result.config); const historyErrors = arrayRecords(result.historyErrors); const deliveryBudget = record(result.deliveryBudget); const warnings = arrayRecords(result.warnings); const detailId = stringValue(result.detailId); const timeHeader = `TIME(${stringValue(config.displayTimeZone)})`; const lines = [ "PLATFORM-INFRA PIPELINES-AS-CODE HISTORY", `DATA: ${stringValue(config.source)}`, `ROWS: ${stringValue(rows.length)} LIMIT_PER_CONSUMER: ${stringValue(config.limitPerConsumer)} READ_ERRORS: ${stringValue(historyErrors.length)}`, `DELIVERY_BUDGET: state=${stringValue(deliveryBudget.state)} budget=${stringValue(record(deliveryBudget.policy).endToEndBudgetSeconds)}s exact=${stringValue(deliveryBudget.exactCommand)}`, ...renderPacConfigWarnings(warnings), ...(historyErrors.length === 0 ? [] : [ "", "READ ERRORS", ...table(["CONTEXT", "STATUS", "ERROR"], historyErrors.map((item) => [stringValue(item.context), stringValue(item.status), compactLine(stringValue(item.error ?? item.stderr))])), ]), "", "TRIGGERS", ...(rows.length === 0 ? ["-"] : table([timeHeader, "CONSUMER", "CLASS", "OWNER", "STATUS", "TRACE", "FIRST_BREAK", "PHASE", "ERROR", "EXIT", "DUR_S", "COMMIT", "ENV_REUSE", "ID"], rows.map((row) => { const firstBreak = record(row.firstBreak); const error = record(row.error); return [ stringValue(row.displayTime) === "-" ? isoShort(stringValue(row.triggeredAt)) : stringValue(row.displayTime), stringValue(row.consumer), stringValue(row.deliveryClass), stringValue(row.deliveryOwner ?? row.executionOwner), statusText(row), short(stringValue(row.traceId), 16), stringValue(firstBreak.code), stringValue(firstBreak.phase ?? error.phase), `${stringValue(error.type)}/${stringValue(error.code)}`, stringValue(error.exitCode), stringValue(row.durationSeconds), short(stringValue(row.commit)), envReuseText(record(row.envReuse)), stringValue(row.id ?? row.pipelineRun), ]; }))), ...(detailId === "-" || rows.length !== 1 ? [] : renderHistoryDetail(rows[0] ?? {})), "", "NEXT", rows.length > 0 && detailId === "-" ? ` detail: ${stringValue(record(result.next).history)} --id ${stringValue(rows[0]?.id ?? rows[0]?.pipelineRun)}` : null, ` full: ${stringValue(record(result.next).history)}${detailId === "-" ? "" : ` --id ${detailId}`} --full`, ]; return rendered(result, "platform-infra pipelines-as-code history", lines.filter((line): line is string => line !== null)); } export function renderDebugStep(result: Record): RenderedCliResult { const checks = arrayRecords(result.checks); const realRun = record(result.realRun); const pipelineRun = record(realRun.pipelineRun); const artifact = record(realRun.artifact); const catalog = record(artifact.catalog); const terminal = arrayRecords(realRun.terminal); const firstBreak = record(realRun.firstBreak); const typedError = record(realRun.error); const fixturePassed = checks.filter((item) => item.ok === true).length; const lines = [ "PLATFORM-INFRA PIPELINES-AS-CODE DEBUG STEP", ...table(["TARGET", "CONSUMER", "PIPELINERUN", "OK", "FIXTURES", "MUTATION"], [[ stringValue(record(result.target).id), stringValue(result.consumer), stringValue(result.detailId), boolText(result.ok), `${fixturePassed}/${checks.length}`, boolText(result.mutation), ]]), ...(Object.keys(realRun).length === 0 ? [] : [ "", "REAL PIPELINERUN EVIDENCE", ...table(["FOUND", "CLASS", "AUTHORITY", "STATUS", "TRACE", "PHASE", "ERROR", "EXIT", "SOURCE", "MODE", "VALID", "FIRST_BREAK"], [[ boolText(realRun.found), stringValue(pipelineRun.deliveryClass), boolText(pipelineRun.deliveryAuthorityEligible), stringValue(pipelineRun.status ?? pipelineRun.reason), short(stringValue(realRun.traceId), 16), stringValue(firstBreak.phase ?? firstBreak.stage), `${stringValue(typedError.type)}/${stringValue(typedError.code)}`, stringValue(typedError.exitCode), short(stringValue(pipelineRun.sourceCommit), 16), stringValue(realRun.mode), boolText(realRun.valid), stringValue(firstBreak.code), ]]), "", "TERMINAL EVIDENCE", ...(terminal.length === 0 ? ["-"] : table(["STEP", "PRESENT", "STATUS", "SOURCE", "MARKER", "TASKRUN", "RESULTS"], terminal.map((item) => [ stringValue(item.step), boolText(item.present), stringValue(item.status), stringValue(item.source), item.markerPresent === true ? stringValue(item.markerSource) : "-", short(stringValue(item.taskRun), 52), compactResultNames(item.results), ]))), "", "ARTIFACT / GITOPS", ...table(["CATALOG", "VERIFIED", "REUSED", "DIGESTS", "GITOPS"], [[ stringValue(catalog.status), boolText(catalog.registryVerified), stringValue(catalog.reusedCount), stringValue(catalog.digestCount), short(stringValue(artifact.gitopsCommit), 16), ]]), renderFeatureConfigSchemaLine(record(artifact.sourceObservation).featureConfigSchema), ]), "", "STATUS EVALUATOR FIXTURES", ...(checks.length === 0 ? ["-"] : table(["CASE", "OK", "EXPECTED", "ACTUAL"], checks.map((item) => [ stringValue(item.id), boolText(item.ok), `${stringValue(item.expectedOk)}/${stringValue(item.expectedCode)}`, `${stringValue(item.actualOk)}/${stringValue(item.actualCode)}`, ]))), "", "NEXT", ` status: ${stringValue(record(result.next).status)}`, ]; return rendered(result, "platform-infra pipelines-as-code debug-step", lines); } function parseApplyOptions(args: string[]): ApplyOptions { const commonArgs: string[] = []; let confirm = false; let dryRun = false; let wait = false; for (let index = 0; index < args.length; index += 1) { const arg = args[index]; if (arg === "--confirm") confirm = true; else if (arg === "--dry-run") dryRun = true; else if (arg === "--wait") wait = true; else { commonArgs.push(arg); if (arg === "--target" || arg === "--node" || arg === "--consumer") { commonArgs.push(args[index + 1] ?? ""); index += 1; } } } if (confirm && dryRun) throw new Error("pipelines-as-code apply accepts only one of --confirm or --dry-run"); return { ...parseCommonOptions(commonArgs), confirm, dryRun: dryRun || !confirm, wait }; } function parseCloseoutOptions(args: string[]): CloseoutOptions { const commonArgs: string[] = []; let sourceCommit: string | null = null; let wait = false; let timeoutMs = DEFAULT_READ_ONLY_LONG_POLL_TIMEOUT_MS; for (let index = 0; index < args.length; index += 1) { const arg = args[index]; if (arg === "--source-commit" || arg === "--commit") { const value = args[index + 1]; if (value === undefined || value.startsWith("--")) throw new Error(`${arg} requires a value`); if (!/^[0-9a-f]{40}$/iu.test(value)) throw new Error(`${arg} must be a full git sha`); sourceCommit = value; index += 1; } else if (arg === "--wait") { wait = true; } else if (arg === "--timeout") { const value = args[index + 1]; if (value === undefined || value.startsWith("--")) throw new Error("--timeout requires a value"); timeoutMs = parseStrictDuration(value, "--timeout"); index += 1; } else { commonArgs.push(arg); if (arg === "--target" || arg === "--node" || arg === "--consumer") { commonArgs.push(args[index + 1] ?? ""); index += 1; } } } return { ...parseCommonOptions(commonArgs), sourceCommit, wait, timeoutMs }; } function parseHistoryOptions(args: string[]): HistoryOptions { const commonArgs: string[] = []; let limit = 5; let detailId: string | null = null; for (let index = 0; index < args.length; index += 1) { const arg = args[index]; if (arg === "--limit") { const value = args[index + 1]; if (value === undefined || value.startsWith("--")) throw new Error("--limit requires a value"); const parsed = Number.parseInt(value, 10); if (!Number.isInteger(parsed) || parsed < 1 || parsed > 50) throw new Error("--limit must be an integer from 1 to 50"); limit = parsed; index += 1; } else if (arg === "--id" || arg === "--run" || arg === "--pipeline-run") { const value = args[index + 1]; if (value === undefined || value.startsWith("--")) throw new Error(`${arg} requires a value`); if (!/^[A-Za-z0-9._:-]+$/u.test(value)) throw new Error(`${arg} must be a PipelineRun id`); detailId = value; index += 1; } else { commonArgs.push(arg); if (arg === "--target" || arg === "--node" || arg === "--consumer") { commonArgs.push(args[index + 1] ?? ""); index += 1; } } } return { ...parseCommonOptions(commonArgs), limit, detailId }; } function parseCommonOptions(args: string[]): CommonOptions { let targetId: string | null = null; let consumerId: string | null = null; let full = false; let raw = false; let json = false; for (let index = 0; index < args.length; index += 1) { const arg = args[index]; if (arg === "--target" || arg === "--node" || arg === "--consumer") { const value = args[index + 1]; if (value === undefined || value.startsWith("--")) throw new Error(`${arg} requires a value`); if (!/^[A-Za-z0-9._-]+$/u.test(value)) throw new Error(`${arg} must be a simple id`); if (arg === "--consumer") consumerId = value; else targetId = value; index += 1; } else if (arg === "--full") { full = true; } else if (arg === "--raw") { raw = true; full = true; } else if (arg === "--json") { json = true; } else { throw new Error(`unsupported pipelines-as-code option: ${arg}`); } } return { targetId, consumerId, full, raw, json }; } function closeoutReady(value: Record, sourceCommit: string | null): boolean { if (value.ok !== true) return false; const summary = record(value.summary); const latest = record(summary.latestPipelineRun); const diagnostics = record(summary.diagnostics); const observedSourceCommit = observedCloseoutSourceCommit(latest, diagnostics); if (sourceCommit === null) return true; return observedSourceCommit === sourceCommit; } function closeoutCiReady(value: Record, sourceCommit: string | null): boolean { const summary = record(value.summary); const latest = record(summary.latestPipelineRun); const diagnostics = record(summary.diagnostics); const pipelineGate = record(summary.pipelineRunGate); if (pipelineGate.ok !== true) return false; const observedSourceCommit = observedCloseoutSourceCommit(latest, diagnostics); if (sourceCommit !== null && observedSourceCommit !== sourceCommit) return false; const sourceObservation = record(summary.sourceObservation); if (Object.keys(sourceObservation).length > 0) { if (sourceObservation.valid !== true || stringValue(sourceObservation.sourceCommit) !== observedSourceCommit) return false; if (stringValue(sourceObservation.contract) === "hwlab-g14-ci-plan" && stringValue(sourceObservation.mode) === "delivery") { const deliveryArtifact = record(summary.artifact); const catalog = record(deliveryArtifact.catalog); const digests = Array.isArray(deliveryArtifact.digests) ? deliveryArtifact.digests : []; if (catalog.present !== true || stringValue(catalog.status) !== "published" || catalog.registryVerified !== true || stringValue(catalog.sourceCommit) !== observedSourceCommit || digests.length === 0) return false; } } if (stringValue(diagnostics.code) === "pac-ready-no-runtime-change") return true; const artifact = record(summary.artifact); if (stringValue(artifact.imageStatus) === "disabled") return true; return stringValue(artifact.gitopsCommit) !== "-"; } function observedCloseoutSourceCommit(latest: Record, diagnostics: Record): string { return stringValue(latest.sourceCommit) !== "-" ? stringValue(latest.sourceCommit) : stringValue(diagnostics.sourceCommit); } function closeoutBlocker(value: Record, expected: string | null, observed: string, sourceMatched: boolean, gitOpsMirrorFlush: unknown): Record { if (!sourceMatched) { return { code: "pac-closeout-source-mismatch", reason: `expected source commit ${expected ?? "-"} but latest observed source commit is ${observed}`, valuesPrinted: false, }; } const summary = record(value.summary); const latest = record(summary.latestPipelineRun); const pipelineGate = pipelineRunGate(latest); if (!pipelineGate.ok) { const longestTask = longestTaskRun(arrayRecords(summary.taskRuns)); const longestTaskText = longestTask === null ? "longestTask=-" : `longestTask=${stringValue(longestTask.name)} status=${statusText(longestTask)} duration=${stringValue(longestTask.durationSeconds)}s`; return { code: pipelineGate.code, reason: `${pipelineGate.reason}; pipelineRun=${stringValue(latest.name)} duration=${stringValue(latest.durationSeconds)}s; ${longestTaskText}`, valuesPrinted: false, }; } const flush = record(gitOpsMirrorFlush); if (flush.ok === false) { const next = record(flush.next); return { code: stringValue(flush.degradedReason, "pac-closeout-gitops-mirror-flush-failed"), reason: `GitOps mirror flush did not complete: mode=${stringValue(flush.mode)} required=${stringValue(flush.required)} next=${stringValue(next.flush ?? next.status)}`, valuesPrinted: false, }; } const diagnostics = record(summary.diagnostics); return { code: stringValue(diagnostics.code, "pac-closeout-not-ready"), reason: stringValue(diagnostics.hint, "PaC status is not ready; inspect status/history for the selected consumer"), valuesPrinted: false, }; } function pipelineRunGate(latest: Record): Record { const name = stringValue(latest.name); if (name === "-") { return { ok: false, code: "pipeline-missing", reason: "no matching PipelineRun is visible yet", valuesPrinted: false, }; } const status = stringValue(latest.status); const reason = stringValue(latest.reason); if (status === "True") { return { ok: true, code: "pipeline-succeeded", reason: reason === "-" ? "PipelineRun completed successfully" : `PipelineRun ${reason}`, valuesPrinted: false, }; } if (status === "False") { return { ok: false, code: "pipeline-failed", reason: reason === "-" ? "PipelineRun completed with failure" : `PipelineRun failed: ${reason}`, valuesPrinted: false, }; } return { ok: false, code: "pipeline-running", reason: statusText(latest) === "-" ? "PipelineRun is not terminal" : `PipelineRun is ${statusText(latest)}`, valuesPrinted: false, }; } function longestTaskRun(taskRuns: Record[]): Record | null { let selected: Record | null = null; let selectedDuration = -1; for (const taskRun of taskRuns) { const duration = numericValue(taskRun.durationSeconds); if (duration === null || duration < selectedDuration) continue; selected = taskRun; selectedDuration = duration; } return selected; } function positiveInteger(obj: Record, key: string, path: string): number { const value = y.integerField(obj, key, path); if (value < 1) throw new Error(`${configLabel}.${path}.${key} must be positive`); return value; } function urlField(obj: Record, key: string, path: string): string { const value = y.stringField(obj, key, path); const parsed = new URL(value); if (!["http:", "https:"].includes(parsed.protocol) || parsed.search || parsed.hash) throw new Error(`${configLabel}.${path}.${key} must be an http(s) URL without query or hash`); return value.replace(/\/+$/u, ""); } function validateTimeZone(value: string, path: string): void { try { new Intl.DateTimeFormat("en-US", { timeZone: value }).format(new Date(0)); } catch (error) { throw new Error(`${path} must be a valid IANA time zone: ${(error as Error).message}`); } } function stringRecord(obj: Record, path: string): Record { const out: Record = {}; for (const [key, value] of Object.entries(obj)) { if (typeof value !== "string" || value.length === 0) throw new Error(`${configLabel}.${path}.${key} must be a non-empty string`); out[key] = value; } return out; } function secretFingerprint(value: string): string { return sha256Fingerprint(value).slice(0, 23); } function rendered(result: Record, command: string, lines: string[]): RenderedCliResult { return { ok: result.ok !== false, command, renderedText: lines.join("\n"), contentType: "text/plain" }; } function record(value: unknown): Record { return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record : {}; } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } function arrayRecords(value: unknown): Record[] { return Array.isArray(value) ? value.filter((item) => typeof item === "object" && item !== null && !Array.isArray(item)) as Record[] : []; } function stringValue(value: unknown, fallback = "-"): string { if (typeof value === "string" && value.length > 0) return value; if (typeof value === "number" || typeof value === "boolean") return String(value); return fallback; } function numericValue(value: unknown): number | null { if (typeof value === "number" && Number.isFinite(value)) return value; if (typeof value !== "string" || value.trim().length === 0) return null; const parsed = Number(value); return Number.isFinite(parsed) ? parsed : null; } function boolText(value: unknown): string { return value === true ? "true" : "false"; } function short(value: string, length = 12): string { if (value === "-" || value.length <= length) return value; return value.slice(0, length); } function isoShort(value: string): string { if (value === "-") return value; return value.replace(/:\d\d(?:\.\d+)?Z$/u, "Z"); } function statusText(row: Record): string { const status = stringValue(row.status); const reason = stringValue(row.reason); if (status === "-" && reason === "-") return "-"; if (status === "True" && reason !== "-") return reason; if (reason === "-") return status; if (status === "-") return reason; return `${status}/${reason}`; } function envReuseText(envReuse: Record): string { const status = stringValue(envReuse.status); const skipped = stringValue(envReuse.buildSkippedCount); const reused = stringValue(envReuse.serviceReusedCount); const cache = stringValue(envReuse.buildCache); const parts = [ status === "-" ? null : status, skipped === "-" ? null : `skip=${skipped}`, reused === "-" ? null : `reuse=${reused}`, cache === "-" ? null : `cache=${cache}`, ].filter((item): item is string => item !== null); return parts.length === 0 ? "-" : parts.join(","); } function stageTimingsText(timings: Record): string { const parts = Object.entries(timings) .filter((entry): entry is [string, number] => typeof entry[1] === "number" && Number.isFinite(entry[1])) .map(([name, value]) => `${name}=${Math.round(value)}${name.endsWith("Seconds") ? "s" : "ms"}`); return parts.length === 0 ? "-" : parts.join(","); } function registryText(registry: Record): string { if (Object.keys(registry).length === 0) return "-"; if (registry.status === "not-configured" || registry.applicability === "not-configured") return "N/A"; const present = registry.present === true ? "present" : "missing"; const digest = short(stringValue(registry.digest), 18); return digest === "-" ? present : `${present}:${digest}`; } function runtimeText(runtime: Record): string { if (Object.keys(runtime).length === 0) return "-"; const ready = `${stringValue(runtime.readyReplicas)}/${stringValue(runtime.replicas)}`; const digest = short(stringValue(runtime.digest), 18); return digest === "-" ? ready : `${ready}:${digest}`; } function renderHistoryDetail(row: Record): string[] { const envReuse = record(row.envReuse); const sourceObservation = record(row.sourceObservation); const deliveryPlan = record(sourceObservation.plan); const terminalSources = record(sourceObservation.terminalSources); const artifact = record(row.artifact); const catalog = record(artifact.catalog); const collector = record(row.collector); const taskRuns = record(row.taskRuns); const longest = record(taskRuns.longest); const failed = record(taskRuns.failed); const failedStep = record(failed.failedStep); const firstBreak = record(row.firstBreak); const typedError = record(row.error); return [ "", "DETAIL", ...table(["FIELD", "VALUE"], [ ["id", stringValue(row.id ?? row.pipelineRun)], ["deliveryClass", stringValue(row.deliveryClass)], ["deliveryAuthorityEligible", stringValue(row.deliveryAuthorityEligible)], ["deliveryOwner", stringValue(row.deliveryOwner)], ["executionOwner", stringValue(row.executionOwner)], ["parentRelation", stringValue(row.parentRelation)], ["pipeline", stringValue(row.pipeline)], ["branch", stringValue(row.branch)], ["commit", stringValue(row.commit)], ["started", stringValue(row.displayStartTime) === "-" ? stringValue(row.startTime) : stringValue(row.displayStartTime)], ["completed", stringValue(row.displayCompletionTime) === "-" ? stringValue(row.completionTime) : stringValue(row.displayCompletionTime)], ["taskRuns", stringValue(taskRuns.count)], ["longestTask", stringValue(longest.name)], ["longestTaskDurationS", stringValue(longest.durationSeconds)], ["failedTask", stringValue(failed.name)], ["failedStep", stringValue(failedStep.name)], ["failedStepExit", stringValue(failedStep.exitCode)], ["failedStepTermination", stringValue(failedStep.terminationReason ?? failedStep.reason)], ["failure", compactLine(stringValue(failed.message))], ["traceId", stringValue(row.traceId)], ["firstBreak", stringValue(firstBreak.code)], ["firstBreakPhase", stringValue(firstBreak.phase)], ["errorType", stringValue(typedError.type)], ["errorCode", stringValue(typedError.code)], ["errorHttpStatus", stringValue(typedError.httpStatus)], ["errorExitCode", stringValue(typedError.exitCode)], ["errorSummary", compactLine(stringValue(typedError.stderrSummary ?? typedError.message))], ["envReuseSource", stringValue(envReuse.source)], ["envIdentity", short(stringValue(envReuse.identity), 24)], ["stageTimings", stageTimingsText(record(envReuse.stageTimings))], ["deliveryMode", stringValue(sourceObservation.mode)], ["deliveryValid", stringValue(sourceObservation.valid)], ["deliverySourceMatched", stringValue(sourceObservation.sourceMatched)], ["deliveryReason", stringValue(sourceObservation.reason)], ["affectedServices", stringValue(deliveryPlan.affectedServiceCount)], ["rolloutServices", stringValue(deliveryPlan.rolloutServiceCount)], ["buildServices", stringValue(deliveryPlan.buildServiceCount)], ["reusedServices", stringValue(deliveryPlan.reusedServiceCount)], ...featureConfigSchemaHistoryFields(sourceObservation.featureConfigSchema), ]), "", "TERMINAL EVIDENCE", ...table(["STEP", "PRESENT", "STATUS", "SOURCE", "MARKER", "TASKRUN"], [ ["plan-artifacts", boolText(record(terminalSources.planArtifacts).present), stringValue(record(terminalSources.planArtifacts).status), stringValue(record(terminalSources.planArtifacts).source), record(terminalSources.planArtifacts).markerPresent === true ? stringValue(record(terminalSources.planArtifacts).markerSource) : "-", short(stringValue(record(terminalSources.planArtifacts).taskRun), 52)], ["collect-artifacts", boolText(record(terminalSources.collectArtifacts).present), stringValue(record(terminalSources.collectArtifacts).status), stringValue(record(terminalSources.collectArtifacts).source), record(terminalSources.collectArtifacts).markerPresent === true ? stringValue(record(terminalSources.collectArtifacts).markerSource) : "-", short(stringValue(record(terminalSources.collectArtifacts).taskRun), 52)], ["gitops-promote", boolText(record(terminalSources.gitopsPromote).present), stringValue(record(terminalSources.gitopsPromote).status), stringValue(record(terminalSources.gitopsPromote).source), record(terminalSources.gitopsPromote).markerPresent === true ? stringValue(record(terminalSources.gitopsPromote).markerSource) : "-", short(stringValue(record(terminalSources.gitopsPromote).taskRun), 52)], ]), "", "ARTIFACT / COLLECTOR", ...table(["CATALOG", "VERIFIED", "REUSED", "DIGESTS", "GITOPS", "TASKS", "TERMINALS", "LOG_ERRORS"], [[ stringValue(catalog.status), boolText(catalog.registryVerified), stringValue(catalog.reusedCount), stringValue(catalog.digestCount), short(stringValue(artifact.gitopsCommit), 16), stringValue(collector.contractTaskCount), stringValue(collector.terminalRecordCount), stringValue(collector.logErrorCount), ]]), "", "LOGS", ` ${stringValue(taskRuns.logsCommand)}`, ]; } function compactLine(value: string): string { const trimmed = value.replace(/\s+/gu, " ").trim(); return trimmed.length > 0 ? trimmed.slice(0, 120) : "-"; } function compactResultNames(value: unknown): string { if (!Array.isArray(value) || value.length === 0) return "-"; const names = value.filter((item): item is string => typeof item === "string"); if (names.length <= 2) return names.join(","); return `${names.length}:${names.slice(0, 2).join(",")},...`; } function table(headers: string[], rows: string[][]): string[] { const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length))); const format = (row: string[]) => row.map((cell, index) => cell.padEnd(widths[index])).join(" "); return [format(headers), format(headers.map((header, index) => "-".repeat(Math.max(header.length, widths[index])))), ...rows.map(format)]; }