fix: preserve hwlab refresh render evidence

This commit is contained in:
Codex
2026-07-04 03:42:47 +00:00
parent 779274b26f
commit e8bf83821a
7 changed files with 391 additions and 7 deletions
@@ -26,8 +26,8 @@ try {
prepareYamlDependency();
applyDeployOverlay();
renderControlPlane();
await applyPipeline();
emit({ ok: true, status: "applied" });
const evidence = await applyPipeline();
emit({ ok: true, status: "applied", ...evidence });
} finally {
rmSync(workDir, { recursive: true, force: true });
}
@@ -143,16 +143,21 @@ async function applyPipeline() {
if (typeof renderedPipelineName !== "string" || renderedPipelineName.length === 0) {
throw new Error(`rendered Pipeline metadata.name missing: ${pipelinePath}`);
}
const render = summarizeRenderedPipeline(pipeline, renderedPipelineName);
const pipelineName = requiredOverlayString("pipelineName");
pipeline.metadata = pipeline.metadata && typeof pipeline.metadata === "object" ? pipeline.metadata : {};
pipeline.metadata.name = pipelineName;
const pipelineText = YAML.stringify(pipeline);
await kubeRequest(
const applyText = await kubeRequest(
"PATCH",
`/apis/tekton.dev/v1/namespaces/${encodeURIComponent(tektonNamespace)}/pipelines/${encodeURIComponent(pipelineName)}?fieldManager=${encodeURIComponent(fieldManager)}&force=true`,
pipelineText,
"application/apply-patch+yaml",
);
return {
render,
apply: summarizeAppliedPipeline(parseJsonObject(applyText), pipelineName, tektonNamespace),
};
}
function yamlModule() {
@@ -281,6 +286,102 @@ function requiredNonNegativeNumber(name) {
return Math.floor(value);
}
function summarizeRenderedPipeline(pipeline, pipelineName) {
const tasks = Array.isArray(pipeline?.spec?.tasks) ? pipeline.spec.tasks : [];
const runtimeReady = tasks.find((task) => recordOrNull(task)?.name === "runtime-ready");
return {
pipelineName,
taskCount: tasks.length,
runtimeReadyTask: summarizeRuntimeReadyTask(runtimeReady),
};
}
function summarizeRuntimeReadyTask(value) {
const task = recordOrNull(value);
if (task === null) return { present: false, name: null, runAfter: [], when: [] };
return {
present: true,
name: stringOrNull(task.name),
runAfter: compactStringArray(task.runAfter, 4),
when: compactWhenList(task.when, 4),
};
}
function summarizeAppliedPipeline(value, pipelineName, namespace) {
const metadata = recordOrNull(value?.metadata);
return {
pipelineName: stringOrNull(metadata?.name) || pipelineName,
namespace: stringOrNull(metadata?.namespace) || namespace,
resourceVersion: stringOrNull(metadata?.resourceVersion),
annotations: compactMetadataMap(metadata?.annotations, [
"sourceConfig",
"ciContract",
"policy",
"hwlab.pikastech.local/source-commit",
"tekton.dev/pipelines.minVersion",
], 6),
labels: compactMetadataMap(metadata?.labels, [
"hwlab.pikastech.local/source-commit",
"app.kubernetes.io/name",
"app.kubernetes.io/part-of",
"app.kubernetes.io/component",
], 6),
degradedReason: metadata === null ? "apply-response-metadata-missing" : null,
};
}
function compactMetadataMap(value, preferredKeys, limit) {
const record = recordOrNull(value);
if (record === null) return null;
const output = {};
for (const key of preferredKeys) {
const item = stringOrNull(record[key]);
if (item === null || output[key] !== undefined) continue;
output[key] = item;
if (Object.keys(output).length >= limit) return output;
}
for (const key of Object.keys(record).sort()) {
const item = stringOrNull(record[key]);
if (item === null || output[key] !== undefined) continue;
output[key] = item;
if (Object.keys(output).length >= limit) break;
}
return Object.keys(output).length === 0 ? null : output;
}
function compactStringArray(value, limit) {
return Array.isArray(value)
? value.map((item) => stringOrNull(item)).filter(Boolean).slice(0, limit)
: [];
}
function compactWhenList(value, limit) {
return Array.isArray(value)
? value.map((item) => recordOrNull(item)).filter(Boolean).slice(0, limit).map((item) => ({
input: stringOrNull(item.input),
operator: stringOrNull(item.operator),
values: compactStringArray(item.values, 4),
}))
: [];
}
function parseJsonObject(text) {
try {
const parsed = JSON.parse(text);
return recordOrNull(parsed);
} catch {
return null;
}
}
function recordOrNull(value) {
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
}
function stringOrNull(value) {
return typeof value === "string" && value.length > 0 ? value : null;
}
function emit(extra) {
process.stdout.write(`${JSON.stringify({
...extra,
+62 -1
View File
@@ -292,16 +292,69 @@ function compactRefreshEvidence(refresh) {
jobName: stringOrNull(value.jobName) ?? stringOrNull(summary.jobName),
namespace: stringOrNull(value.namespace) ?? stringOrNull(summary.namespace),
status: stringOrNull(summary.status),
pipeline: stringOrNull(summary.pipeline),
pipeline: stringOrNull(summary.pipeline) || stringOrNull(recordOrNull(summary.apply)?.pipelineName) || stringOrNull(recordOrNull(summary.render)?.pipelineName),
sourceCommit: stringOrNull(summary.sourceCommit),
sourceStageRef: stringOrNull(summary.sourceStageRef),
elapsedMs: numberOrNull(summary.elapsedMs),
render: compactRefreshRender(recordOrNull(summary.render)),
apply: compactRefreshApply(recordOrNull(summary.apply)),
sourceAuthority: stringOrNull(summary.sourceAuthority),
statusAuthority: stringOrNull(summary.statusAuthority),
parsedDownstreamCliOutput: false,
};
}
function compactRefreshRender(value) {
if (value === null) return null;
return {
pipelineName: stringOrNull(value.pipelineName),
taskCount: numberOrNull(value.taskCount),
runtimeReadyTask: compactRefreshRuntimeReady(recordOrNull(value.runtimeReadyTask)),
};
}
function compactRefreshApply(value) {
if (value === null) return null;
return {
pipelineName: stringOrNull(value.pipelineName),
namespace: stringOrNull(value.namespace),
resourceVersion: stringOrNull(value.resourceVersion),
annotations: compactStringMap(recordOrNull(value.annotations)),
labels: compactStringMap(recordOrNull(value.labels)),
degradedReason: stringOrNull(value.degradedReason),
};
}
function compactRefreshRuntimeReady(value) {
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, limit) {
return Array.isArray(value)
? value.map((item) => recordOrNull(item)).filter(Boolean).slice(0, limit).map((item) => ({
input: stringOrNull(item.input),
operator: stringOrNull(item.operator),
values: compactStringArray(item.values, 4),
}))
: [];
}
function compactStringMap(value) {
if (value === null) return null;
const output = {};
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 compactArgo(argo) {
const value = recordOrNull(argo);
if (value === null) return null;
@@ -492,6 +545,14 @@ function numberOrNull(value) {
return typeof value === "number" && Number.isFinite(value) ? value : null;
}
function booleanOrNull(value) {
return value === true ? true : value === false ? false : null;
}
function compactStringArray(value, limit) {
return Array.isArray(value) ? value.map((item) => stringOrNull(item)).filter(Boolean).slice(0, limit) : [];
}
const result = await readConfigMap();
const errors = [];
const stateByFollower = {};
+66 -1
View File
@@ -457,16 +457,73 @@ function compactRefreshEvidence(value: unknown): Record<string, unknown> | null
jobName: stringOrNull(refresh.jobName),
namespace: stringOrNull(refresh.namespace),
status: stringOrNull(refresh.status),
pipeline: stringOrNull(refresh.pipeline),
pipeline: stringOrNull(refresh.pipeline) ?? stringOrNull(asOptionalRecord(refresh.apply)?.pipelineName) ?? stringOrNull(asOptionalRecord(refresh.render)?.pipelineName),
sourceCommit: stringOrNull(refresh.sourceCommit),
sourceStageRef: stringOrNull(refresh.sourceStageRef),
elapsedMs: numberOrNull(refresh.elapsedMs),
render: compactRefreshRender(asOptionalRecord(refresh.render)),
apply: compactRefreshApply(asOptionalRecord(refresh.apply)),
sourceAuthority: stringOrNull(refresh.sourceAuthority),
statusAuthority: stringOrNull(refresh.statusAuthority),
parsedDownstreamCliOutput: false,
};
}
function compactRefreshRender(value: Record<string, unknown> | null): Record<string, unknown> | null {
if (value === null) return null;
return {
pipelineName: stringOrNull(value.pipelineName),
taskCount: numberOrNull(value.taskCount),
runtimeReadyTask: compactRefreshRuntimeReady(asOptionalRecord(value.runtimeReadyTask)),
};
}
function compactRefreshApply(value: Record<string, unknown> | null): Record<string, unknown> | 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<string, unknown> | null): Record<string, unknown> | 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<Record<string, unknown>> {
return Array.isArray(value)
? value
.map((item) => asOptionalRecord(item))
.filter((item): item is Record<string, unknown> => item !== null)
.slice(0, limit)
.map((item) => ({
input: stringOrNull(item.input),
operator: stringOrNull(item.operator),
values: compactStringArray(item.values, 4),
}))
: [];
}
function compactStringMap(value: Record<string, unknown> | null): Record<string, string> | null {
if (value === null) return null;
const output: Record<string, string> = {};
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 arrayRecords(value: unknown): Record<string, unknown>[] {
return Array.isArray(value) ? value.filter((item): item is Record<string, unknown> => typeof item === "object" && item !== null && !Array.isArray(item)) : [];
}
@@ -555,6 +612,14 @@ 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) : [];
}
function shortSha(value: string | null): string {
if (value === null) return "-";
return value.length > 12 ? value.slice(0, 12) : value;
+46
View File
@@ -37,6 +37,7 @@ function renderJobHuman(payload: Record<string, unknown>): string {
const pods = arrayRecords(result?.pods);
const logs = arrayRecords(result?.logs);
const errors = arrayRecords(result?.errors);
const summaryEvidence = refreshEvidenceRows(asOptionalRecord(result?.summary));
const command = asOptionalRecord(payload.command);
const identity = asOptionalRecord(command?.identity);
return [
@@ -59,6 +60,7 @@ function renderJobHuman(payload: Record<string, unknown>): string {
pods.length === 0 ? "" : `\nPODS\n${table(["POD", "PHASE", "READY", "START", "CONTAINERS", "REASON"], pods.map(jobPodRow))}`,
logs.length === 0 ? "" : `\nLOG TAILS\n${table(["POD", "CONTAINER", "STATUS", "REASON", "LINES", "BYTES", "TIMING", "MESSAGE"], logs.map(logRow))}`,
errors.length === 0 ? "" : `\nERRORS\n${table(["POD", "CONTAINER", "REASON", "MESSAGE"], errors.map((item) => [item.pod, item.container, item.degradedReason, item.message]))}`,
summaryEvidence.length === 0 ? "" : `\nEVIDENCE\n${table(["TYPE", "STATUS", "DETAIL", "OBJECT"], summaryEvidence)}`,
command === null ? "" : `\nTARGET COMMAND\n${table(["ROUTE", "SCRIPT", "EXIT", "PARSE_ERROR"], [[identity?.route ?? "-", identity?.script ?? "-", command.exitCode ?? "-", command.parseError ?? "-"]])}`,
command?.stdoutTail ? `\nSTDOUT_TAIL\n${command.stdoutTail}` : "",
command?.stderrTail ? `\nSTDERR_TAIL\n${command.stderrTail}` : "",
@@ -287,11 +289,37 @@ function nativeGateRows(native: Record<string, unknown> | null): unknown[][] {
`${shortSha(stringOrNull(refresh.sourceCommit))}/${stringOrNull(refresh.pipeline) ?? "-"}`,
stringOrNull(refresh.jobName) ?? "-",
]);
rows.push(...refreshEvidenceRows(refresh));
}
for (const error of arrayTextItems(native.errors).slice(0, 5)) rows.push(["error", "present", error, "-"]);
return rows;
}
function refreshEvidenceRows(value: Record<string, unknown> | null): unknown[][] {
if (value === null) return [];
const rows: unknown[][] = [];
const render = asOptionalRecord(value.render);
const renderRuntimeReady = asOptionalRecord(render?.runtimeReadyTask);
if (render !== null) {
rows.push([
"control-plane-render",
renderRuntimeReady?.present === true ? "runtime-ready-present" : renderRuntimeReady?.present === false ? "runtime-ready-absent" : "-",
whenSummary(arrayRecords(renderRuntimeReady?.when)[0]),
stringOrNull(render.pipelineName) ?? "-",
]);
}
const apply = asOptionalRecord(value.apply);
if (apply !== null) {
rows.push([
"control-plane-apply",
stringOrNull(apply.resourceVersion) ?? stringOrNull(apply.degradedReason) ?? "-",
applyMetadataSummary(apply),
stringOrNull(apply.pipelineName) ?? "-",
]);
}
return rows;
}
function taskRunDetail(item: Record<string, unknown> | undefined): string {
if (item === undefined) return "-";
const duration = numberOrNull(item.durationSeconds);
@@ -308,6 +336,24 @@ function argoDetail(argo: Record<string, unknown>): string {
?? shortSha(stringOrNull(argo.revision));
}
function whenSummary(value: Record<string, unknown> | undefined): string {
if (value === undefined) return "-";
const values = arrayTextItems(value.values).join(",");
return `${stringOrNull(value.input) ?? "-"} ${stringOrNull(value.operator) ?? "-"} ${values || "-"}`;
}
function applyMetadataSummary(value: Record<string, unknown>): string {
const annotations = asOptionalRecord(value.annotations);
const labels = asOptionalRecord(value.labels);
return `ann:${firstEntry(annotations)} label:${firstEntry(labels)}`;
}
function firstEntry(value: Record<string, unknown> | null): string {
if (value === null) return "-";
const [key, item] = Object.entries(value)[0] ?? [];
return key === undefined ? "-" : `${key}=${stringOrNull(item) ?? "-"}`;
}
function asOptionalRecord(value: unknown): Record<string, unknown> | null {
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null;
}
+71 -2
View File
@@ -9,10 +9,12 @@ export function compactRefreshEvidence(value: Record<string, unknown> | null): R
jobName: stringOrNull(value.jobName) ?? stringOrNull(summary.jobName),
namespace: stringOrNull(value.namespace) ?? stringOrNull(summary.namespace),
status: stringOrNull(summary.status),
pipeline: stringOrNull(summary.pipeline),
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,
@@ -34,7 +36,10 @@ export function followerEvidenceSummary(input: {
if (tekton === null && pipeline === null && refresh === null) return null;
const pipelineRefName = stringOrNull(tekton?.pipelineRefName);
const pipelineName = stringOrNull(asOptionalRecord(pipeline?.metadata)?.name);
const refreshPipeline = stringOrNull(refresh?.pipeline);
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,
@@ -46,11 +51,67 @@ export function followerEvidenceSummary(input: {
...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 compactRefreshRender(value: Record<string, unknown> | null): Record<string, unknown> | null {
if (value === null) return null;
return {
pipelineName: stringOrNull(value.pipelineName),
taskCount: numberOrNull(value.taskCount),
runtimeReadyTask: compactRefreshRuntimeReady(asOptionalRecord(value.runtimeReadyTask)),
};
}
function compactRefreshApply(value: Record<string, unknown> | null): Record<string, unknown> | 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<string, unknown> | null): Record<string, unknown> | 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<Record<string, unknown>> {
return Array.isArray(value)
? value
.map((item) => asOptionalRecord(item))
.filter((item): item is Record<string, unknown> => item !== null)
.slice(0, limit)
.map((item) => ({
input: stringOrNull(item.input),
operator: stringOrNull(item.operator),
values: compactStringArray(item.values, 4),
}))
: [];
}
function compactStringMap(value: Record<string, unknown> | null): Record<string, string> | null {
if (value === null) return null;
const output: Record<string, string> = {};
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<Record<string, unknown> | null>): Record<string, unknown> | null {
for (const value of values) {
if (value !== null) return value;
@@ -69,3 +130,11 @@ function stringOrNull(value: unknown): string | 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) : [];
}
+40
View File
@@ -264,6 +264,27 @@ function evidenceRowsForFollower(item: Record<string, unknown>): unknown[][] {
: `${shortSha(stringOrNull(refresh.sourceCommit))}/${boolMatch(refresh.pipelineRefMatches)}/${boolMatch(refresh.pipelineSpecMatches)}`,
refresh === null ? "-" : stringOrNull(refresh.pipeline) ?? "-",
]);
const refreshRender = asOptionalRecord(refresh?.render);
const refreshRenderRuntimeReady = asOptionalRecord(refreshRender?.runtimeReadyTask);
if (refreshRender !== null) {
rows.push([
item.id,
"refresh-render",
refreshRenderRuntimeReady?.present === true ? "runtime-ready-present" : refreshRenderRuntimeReady?.present === false ? "runtime-ready-absent" : "-",
whenSummary(arrayRecords(refreshRenderRuntimeReady?.when)[0]),
stringOrNull(refreshRender.pipelineName) ?? "-",
]);
}
const refreshApply = asOptionalRecord(refresh?.apply);
if (refreshApply !== null) {
rows.push([
item.id,
"refresh-apply",
stringOrNull(refreshApply.resourceVersion) ?? stringOrNull(refreshApply.degradedReason) ?? "-",
applyMetadataSummary(refreshApply),
stringOrNull(refreshApply.pipelineName) ?? "-",
]);
}
return rows;
}
@@ -313,6 +334,25 @@ function boolMatch(value: unknown): string {
return value === true ? "match" : value === false ? "mismatch" : "-";
}
function whenSummary(value: Record<string, unknown> | undefined): string {
if (value === undefined) return "-";
const values = arrayText(value.values);
return `${stringOrNull(value.input) ?? "-"} ${stringOrNull(value.operator) ?? "-"} ${values || "-"}`;
}
function applyMetadataSummary(value: Record<string, unknown>): string {
const annotations = asOptionalRecord(value.annotations);
const labels = asOptionalRecord(value.labels);
const annotation = annotations === null ? "-" : `${firstEntry(annotations)}`;
const label = labels === null ? "-" : `${firstEntry(labels)}`;
return `ann:${annotation} label:${label}`;
}
function firstEntry(value: Record<string, unknown>): string {
const [key, item] = Object.entries(value)[0] ?? [];
return key === undefined ? "-" : `${key}=${stringOrNull(item) ?? "-"}`;
}
function asOptionalRecord(value: unknown): Record<string, unknown> | null {
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null;
}