fix: classify D601 provider health signals
This commit is contained in:
@@ -24,6 +24,12 @@ export type ProviderBlockingDisposition =
|
||||
| "service-degraded"
|
||||
| "global-blocker";
|
||||
|
||||
export type ProviderTriageDecision =
|
||||
| "healthy"
|
||||
| "retryable-transient"
|
||||
| "service-degraded"
|
||||
| "global-offline";
|
||||
|
||||
export interface ProviderTriageSignal {
|
||||
id: string;
|
||||
scope: ProviderSignalScope;
|
||||
@@ -36,11 +42,15 @@ export interface ProviderTriageSignal {
|
||||
|
||||
export interface ProviderTriageClassification {
|
||||
scope: ProviderSignalScope;
|
||||
decision: ProviderTriageDecision;
|
||||
observedAt: string;
|
||||
retryable: boolean;
|
||||
recommendedCrossChecks: string[];
|
||||
blockingDisposition: ProviderBlockingDisposition;
|
||||
rationale: string[];
|
||||
failedScopes: ProviderSignalScope[];
|
||||
degradedScopes: ProviderSignalScope[];
|
||||
healthyScopes: ProviderSignalScope[];
|
||||
failedIndependentScopes: ProviderSignalScope[];
|
||||
healthyIndependentScopes: ProviderSignalScope[];
|
||||
}
|
||||
@@ -193,8 +203,23 @@ function sshSignal(result: unknown, providerId: string): ProviderTriageSignal {
|
||||
function registrySignal(result: unknown): ProviderTriageSignal {
|
||||
const record = asRecord(result);
|
||||
if (record === null) return signal("artifact-registry-health", "registry", "unknown", "artifact registry health returned non-object", result);
|
||||
const status: ProviderSignalStatus = record.ok === true && record.healthy !== false ? "ok" : record.ok === false ? "failed" : "degraded";
|
||||
return signal("artifact-registry-health", "registry", status, `artifact registry health ok=${String(record.ok)} healthy=${String(record.healthy)}`, {
|
||||
const checks = asRecord(record.checks) ?? {};
|
||||
const runtimeApiHealthy = checks.containerRunning === true && checks.loopbackOnly === true && checks.v2Ok === true;
|
||||
const status: ProviderSignalStatus = record.ok === true && record.healthy !== false
|
||||
? "ok"
|
||||
: runtimeApiHealthy
|
||||
? "degraded"
|
||||
: record.ok === false
|
||||
? "failed"
|
||||
: "degraded";
|
||||
return signal("artifact-registry-health", "registry", status, [
|
||||
`artifact registry health ok=${String(record.ok)}`,
|
||||
`healthy=${String(record.healthy)}`,
|
||||
`unitActive=${String(checks.unitActive)}`,
|
||||
`containerRunning=${String(checks.containerRunning)}`,
|
||||
`loopbackOnly=${String(checks.loopbackOnly)}`,
|
||||
`v2Ok=${String(checks.v2Ok)}`,
|
||||
].join(" "), {
|
||||
ok: record.ok,
|
||||
installed: record.installed ?? null,
|
||||
healthy: record.healthy ?? null,
|
||||
@@ -273,9 +298,9 @@ export function providerTriageRecommendedCrossChecks(providerId: string): string
|
||||
];
|
||||
}
|
||||
|
||||
function uniqueScopes(signals: ProviderTriageSignal[], statuses: ProviderSignalStatus[]): ProviderSignalScope[] {
|
||||
function uniqueScopes(signals: ProviderTriageSignal[], statuses: ProviderSignalStatus[], independentOnly = true): ProviderSignalScope[] {
|
||||
return Array.from(new Set(signals
|
||||
.filter((item) => item.independentPath)
|
||||
.filter((item) => !independentOnly || item.independentPath)
|
||||
.filter((item) => statuses.includes(item.status))
|
||||
.map((item) => item.scope)))
|
||||
.sort();
|
||||
@@ -292,10 +317,11 @@ function primaryScope(signals: ProviderTriageSignal[]): ProviderSignalScope {
|
||||
}
|
||||
|
||||
export function classifyProviderTriage(providerId: string, signals: ProviderTriageSignal[], observedAt = isoNow()): ProviderTriageClassification {
|
||||
const failedScopes = uniqueScopes(signals, ["failed"]);
|
||||
const degradedScopes = uniqueScopes(signals, ["degraded"]);
|
||||
const failedScopes = uniqueScopes(signals, ["failed"], false);
|
||||
const degradedScopes = uniqueScopes(signals, ["degraded"], false);
|
||||
const healthyScopes = uniqueScopes(signals, ["ok"]);
|
||||
const independentFailedScopes = failedScopes.filter((scope) => scope !== "runner-local");
|
||||
const independentFailedScopes = uniqueScopes(signals, ["failed"]).filter((scope) => scope !== "runner-local");
|
||||
const independentDegradedScopes = uniqueScopes(signals, ["degraded"]);
|
||||
const failedCriticalScopes = independentFailedScopes.filter((scope) => criticalScopes.has(scope));
|
||||
const runnerLocalObservedFailure = signals.some((signal) => signal.scope === "runner-local" && signal.status === "failed");
|
||||
const serviceOnlyFailure = independentFailedScopes.length > 0 && independentFailedScopes.every((scope) => scope === "registry" || scope === "service-proxy" || scope === "microservice" || scope === "k3s");
|
||||
@@ -312,7 +338,7 @@ export function classifyProviderTriage(providerId: string, signals: ProviderTria
|
||||
} else if (serviceOnlyFailure && hasIndependentHealthy) {
|
||||
blockingDisposition = "service-degraded";
|
||||
rationale.push("service-scoped path failed while at least one provider-level path remains healthy");
|
||||
} else if (failedCriticalScopes.length > 0 || degradedScopes.some((scope) => criticalScopes.has(scope))) {
|
||||
} else if (failedCriticalScopes.length > 0 || independentDegradedScopes.some((scope) => criticalScopes.has(scope))) {
|
||||
blockingDisposition = hasIndependentHealthy ? "provider-degraded" : "transient";
|
||||
rationale.push(hasIndependentHealthy
|
||||
? "provider-critical path is degraded but cross-checks still show independent healthy evidence"
|
||||
@@ -327,15 +353,27 @@ export function classifyProviderTriage(providerId: string, signals: ProviderTria
|
||||
|
||||
if (runnerLocalObservedFailure) rationale.push("runner-local observation failed but is not counted as an independent global blocker by contract");
|
||||
if (hasIndependentHealthy) rationale.push(`healthy independent scopes: ${healthyScopes.join(", ")}`);
|
||||
if (failedScopes.length > 0) rationale.push(`failed independent scopes: ${failedScopes.join(", ")}`);
|
||||
if (failedScopes.length > 0) rationale.push(`failed scopes: ${failedScopes.join(", ")}`);
|
||||
const hasAnyIssueSignal = signals.some((item) => item.status === "failed" || item.status === "degraded");
|
||||
const decision: ProviderTriageDecision = blockingDisposition === "global-blocker"
|
||||
? "global-offline"
|
||||
: blockingDisposition === "service-degraded"
|
||||
? "service-degraded"
|
||||
: hasAnyIssueSignal
|
||||
? "retryable-transient"
|
||||
: "healthy";
|
||||
|
||||
return {
|
||||
scope: runnerLocalObservedFailure && failedScopes.length === 0 && degradedScopes.length === 0 ? "runner-local" : primaryScope(signals),
|
||||
scope: runnerLocalObservedFailure && independentFailedScopes.length === 0 && independentDegradedScopes.length === 0 ? "runner-local" : primaryScope(signals),
|
||||
decision,
|
||||
observedAt,
|
||||
retryable: blockingDisposition !== "global-blocker",
|
||||
recommendedCrossChecks: providerTriageRecommendedCrossChecks(providerId),
|
||||
blockingDisposition,
|
||||
rationale,
|
||||
failedScopes,
|
||||
degradedScopes,
|
||||
healthyScopes,
|
||||
failedIndependentScopes: independentFailedScopes,
|
||||
healthyIndependentScopes: healthyScopes,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user