// SPEC: PJ2026-01060703 CI/CD branch follower bounded evidence helpers. // Responsibility: compact pipeline/runtime-ready and refresh evidence for status/drill-down output. export function compactRefreshEvidence(value: Record | null): Record | null { if (value === null) return null; const summary = asOptionalRecord(value.summary); if (summary === null) return null; return { jobName: stringOrNull(value.jobName) ?? stringOrNull(summary.jobName), namespace: stringOrNull(value.namespace) ?? stringOrNull(summary.namespace), status: stringOrNull(summary.status), pipeline: stringOrNull(summary.pipeline) ?? stringOrNull(asOptionalRecord(summary.apply)?.pipelineName) ?? stringOrNull(asOptionalRecord(summary.render)?.pipelineName), sourceCommit: stringOrNull(summary.sourceCommit), sourceStageRef: stringOrNull(summary.sourceStageRef), elapsedMs: numberOrNull(summary.elapsedMs), render: compactRefreshRender(asOptionalRecord(summary.render)), apply: compactRefreshApply(asOptionalRecord(summary.apply)), sourceAuthority: stringOrNull(summary.sourceAuthority), statusAuthority: stringOrNull(summary.statusAuthority), parsedDownstreamCliOutput: false, }; } export function followerEvidenceSummary(input: { observedSha: string | null; livePayload: Record | null; storedCommand: Record | null; fallbackStoredCommand?: Record | null; }): Record | null { const livePayload = input.livePayload; const storedPayload = asOptionalRecord(input.storedCommand?.payload); const fallbackStoredPayload = asOptionalRecord(input.fallbackStoredCommand?.payload); const tekton = firstRecord(asOptionalRecord(livePayload?.tekton), asOptionalRecord(storedPayload?.tekton), asOptionalRecord(fallbackStoredPayload?.tekton)); const pipeline = firstRecord(asOptionalRecord(livePayload?.pipeline), asOptionalRecord(storedPayload?.pipeline), asOptionalRecord(fallbackStoredPayload?.pipeline)); const refresh = firstRecord(asOptionalRecord(storedPayload?.refreshEvidence), asOptionalRecord(fallbackStoredPayload?.refreshEvidence), asOptionalRecord(livePayload?.refreshEvidence)); if (tekton === null && pipeline === null && refresh === null) return null; const pipelineRefName = stringOrNull(tekton?.pipelineRefName); const pipelineName = stringOrNull(asOptionalRecord(pipeline?.metadata)?.name); const refreshRender = asOptionalRecord(refresh?.render); const refreshApply = asOptionalRecord(refresh?.apply); const refreshRenderedPipeline = stringOrNull(refreshRender?.pipelineName); const refreshPipeline = stringOrNull(refreshApply?.pipelineName) ?? stringOrNull(refresh?.pipeline); const refreshSourceCommit = stringOrNull(refresh?.sourceCommit); return { pipelineRunRefName: pipelineRefName, pipeline: compactPipelineEvidence(pipeline), refreshBoundedReason: refresh === null ? "missing-control-plane-refresh-capability-evidence; status refresh job is reported separately at top-level refresh/liveRefresh" : null, refresh: refresh === null ? null : { ...refresh, pipelineRefMatches: pipelineRefName === null || refreshPipeline === null ? null : pipelineRefName === refreshPipeline, pipelineSpecMatches: pipelineName === null || refreshPipeline === null ? null : pipelineName === refreshPipeline, renderedPipelineMatchesApplied: refreshRenderedPipeline === null || refreshPipeline === null ? null : refreshRenderedPipeline === refreshPipeline, sourceCommitMatches: input.observedSha === null || refreshSourceCommit === null ? null : input.observedSha === refreshSourceCommit, }, }; } function compactPipelineEvidence(value: Record | null): Record | null { if (value === null) return null; const metadata = asOptionalRecord(value.metadata); const spec = asOptionalRecord(value.spec); return { metadata: { name: stringOrNull(metadata?.name) }, spec: { runtimeReadyTask: compactRefreshRuntimeReady(asOptionalRecord(spec?.runtimeReadyTask)), }, }; } function compactRefreshRender(value: Record | null): Record | null { if (value === null) return null; return { pipelineName: stringOrNull(value.pipelineName), taskCount: numberOrNull(value.taskCount), runtimeReadyTask: compactRefreshRuntimeReady(asOptionalRecord(value.runtimeReadyTask)), }; } function compactRefreshApply(value: Record | null): Record | null { if (value === null) return null; return { pipelineName: stringOrNull(value.pipelineName), namespace: stringOrNull(value.namespace), resourceVersion: stringOrNull(value.resourceVersion), annotations: compactStringMap(asOptionalRecord(value.annotations)), labels: compactStringMap(asOptionalRecord(value.labels)), degradedReason: stringOrNull(value.degradedReason), }; } function compactRefreshRuntimeReady(value: Record | null): Record | null { if (value === null) return null; return { present: booleanOrNull(value.present), name: stringOrNull(value.name), runAfter: compactStringArray(value.runAfter, 4), when: compactWhenList(value.when, 4), }; } function compactWhenList(value: unknown, limit: number): Array> { return Array.isArray(value) ? value .map((item) => asOptionalRecord(item)) .filter((item): item is Record => item !== null) .slice(0, limit) .map((item) => ({ input: stringOrNull(item.input), operator: stringOrNull(item.operator), values: compactStringArray(item.values, 4), })) : []; } function compactStringMap(value: Record | null): Record | null { if (value === null) return null; const output: Record = {}; for (const [key, item] of Object.entries(value).slice(0, 8)) { const text = stringOrNull(item); if (text !== null) output[key] = text; } return Object.keys(output).length === 0 ? null : output; } function firstRecord(...values: Array | null>): Record | null { for (const value of values) { if (value !== null) return value; } return null; } function asOptionalRecord(value: unknown): Record | null { return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record : null; } function stringOrNull(value: unknown): string | null { return typeof value === "string" && value.length > 0 ? value : null; } function numberOrNull(value: unknown): number | null { return typeof value === "number" && Number.isFinite(value) ? value : null; } function booleanOrNull(value: unknown): boolean | null { return value === true ? true : value === false ? false : null; } function compactStringArray(value: unknown, limit: number): string[] { return Array.isArray(value) ? value.map((item) => stringOrNull(item)).filter((item): item is string => item !== null).slice(0, limit) : []; }