fix: expose argo closeout reasons

This commit is contained in:
Codex
2026-07-03 18:59:30 +00:00
parent 9b158e2595
commit 979bd33a72
6 changed files with 45 additions and 1 deletions
@@ -46,6 +46,8 @@ Follower-scoped commands such as `status --follower`, `events --follower`, `logs
`status-read`, `events`, `logs` and debug summaries must expose compact closeout gate details when a follower is not aligned: git-mirror readiness, Tekton PipelineRun condition, Argo sync/health, runtime target sha/readiness and short errors. Repeating only phase/observed/target/message is a visibility defect and must be fixed before further rollout tuning.
Argo closeout visibility must include the bounded reason for non-ready health, not only `Synced/Progressing`: health message, operation phase/message, short Application conditions and a small list of non-healthy resources when available.
Stage timing rows must not label optional gates as `not-ready` when they are not part of that follower's closeout contract. For sentinel-like followers without a GitOps branch flush gate, git-mirror source snapshot readiness should render as source-ready/ready, while missing GitOps `githubInSync` remains `-`/not-applicable instead of a failure-looking state.
## Source Authority
@@ -91,6 +91,18 @@ if (key === "pipelineRun") {
statusAuthority: "kubernetes-api-serviceaccount",
};
} else if (key === "argoApplication") {
const resources = Array.isArray(input?.status?.resources) ? input.status.resources : [];
const nonReadyResources = resources
.filter((item) => item?.health?.status && item.health.status !== "Healthy")
.slice(0, 8)
.map((item) => ({
kind: item.kind || null,
namespace: item.namespace || null,
name: item.name || null,
status: item.status || null,
healthStatus: item.health?.status || null,
healthMessage: item.health?.message || null,
}));
output = {
apiVersion: input.apiVersion,
kind: input.kind,
@@ -98,6 +110,10 @@ if (key === "pipelineRun") {
status: {
sync: input?.status?.sync || null,
health: input?.status?.health || null,
conditions: Array.isArray(input?.status?.conditions)
? input.status.conditions.slice(0, 8).map((item) => ({ type: item.type || null, message: item.message || null, lastTransitionTime: item.lastTransitionTime || null }))
: [],
nonReadyResources,
operationState: input?.status?.operationState
? { phase: input.status.operationState.phase || null, message: input.status.operationState.message || null, finishedAt: input.status.operationState.finishedAt || null }
: null,
@@ -190,7 +190,12 @@ function compactArgo(argo) {
name: stringOrNull(value.name),
syncStatus: stringOrNull(value.syncStatus),
healthStatus: stringOrNull(value.healthStatus),
healthMessage: stringOrNull(value.healthMessage),
revision: stringOrNull(value.revision),
operationPhase: stringOrNull(value.operationPhase),
operationMessage: stringOrNull(value.operationMessage),
conditions: arrayRecords(value.conditions).slice(0, 5),
nonReadyResources: arrayRecords(value.nonReadyResources).slice(0, 5),
ready: value.ready === true,
};
}
+5
View File
@@ -315,7 +315,12 @@ function compactStatusGates(payload: Record<string, unknown> | null): Record<str
argo: argo === null ? null : {
syncStatus: stringOrNull(argo.syncStatus),
healthStatus: stringOrNull(argo.healthStatus),
healthMessage: stringOrNull(argo.healthMessage),
revision: stringOrNull(argo.revision),
operationPhase: stringOrNull(argo.operationPhase),
operationMessage: stringOrNull(argo.operationMessage),
conditions: Array.isArray(argo.conditions) ? argo.conditions.slice(0, 5) : [],
nonReadyResources: Array.isArray(argo.nonReadyResources) ? argo.nonReadyResources.slice(0, 5) : [],
ready: argo.ready === true,
},
runtime: runtime === null ? null : {
+11 -1
View File
@@ -47,7 +47,7 @@ function nativeGateRows(native: Record<string, unknown> | null): unknown[][] {
}
const argo = asOptionalRecord(native.argo);
if (argo !== null) {
rows.push(["argo", `${stringOrNull(argo.syncStatus) ?? "unknown"}/${stringOrNull(argo.healthStatus) ?? "unknown"}`, shortSha(stringOrNull(argo.revision)), stringOrNull(argo.name) ?? "-"]);
rows.push(["argo", `${stringOrNull(argo.syncStatus) ?? "unknown"}/${stringOrNull(argo.healthStatus) ?? "unknown"}`, argoDetail(argo), stringOrNull(argo.name) ?? "-"]);
}
const runtime = asOptionalRecord(native.runtime);
if (runtime !== null) {
@@ -58,6 +58,16 @@ function nativeGateRows(native: Record<string, unknown> | null): unknown[][] {
return rows;
}
function argoDetail(argo: Record<string, unknown>): string {
const resource = arrayRecords(argo.nonReadyResources)[0];
const condition = arrayRecords(argo.conditions)[0];
return stringOrNull(argo.healthMessage)
?? stringOrNull(argo.operationMessage)
?? (resource === undefined ? null : `${resource.kind ?? "resource"}/${resource.name ?? "-"} ${asOptionalRecord(resource.health)?.status ?? resource.healthStatus ?? "-"}`)
?? (condition === undefined ? null : `${condition.type ?? "condition"} ${condition.message ?? ""}`.trim())
?? shortSha(stringOrNull(argo.revision));
}
function asOptionalRecord(value: unknown): Record<string, unknown> | null {
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null;
}
+6
View File
@@ -1722,12 +1722,18 @@ function nativeArgoSummary(application: Record<string, unknown> | null): Record<
const status = asOptionalRecord(application.status);
const sync = asOptionalRecord(status?.sync);
const health = asOptionalRecord(status?.health);
const operationState = asOptionalRecord(status?.operationState);
return {
name: stringOrNull(metadata?.name),
namespace: stringOrNull(metadata?.namespace),
syncStatus: stringOrNull(sync?.status),
healthStatus: stringOrNull(health?.status),
healthMessage: stringOrNull(health?.message),
revision: stringOrNull(sync?.revision),
operationPhase: stringOrNull(operationState?.phase),
operationMessage: stringOrNull(operationState?.message),
conditions: Array.isArray(status?.conditions) ? status.conditions.slice(0, 5) : [],
nonReadyResources: Array.isArray(status?.nonReadyResources) ? status.nonReadyResources.slice(0, 5) : [],
ready: argoApplicationReady(application),
};
}