const readyQueryResults = Object.freeze(["durable_readiness_ready", "runtime_durable_ready", "query_ready", "readiness_ready", "ready", "ok", "pass", "passed"]); const blockedQueryResults = Object.freeze>({ driver_missing: "runtime_durable_adapter_driver_missing", ssl_negotiation_blocked: "runtime_durable_adapter_ssl_blocked", auth_blocked: "runtime_durable_adapter_auth_blocked", schema_blocked: "runtime_durable_adapter_schema_blocked", migration_blocked: "runtime_durable_adapter_migration_blocked", query_blocked: "runtime_durable_adapter_query_blocked", not_ready: "runtime_durable_adapter_query_blocked" }); const gateLayers = Object.freeze(["ssl", "auth", "schema", "migration", "durability"]); export function classifyRuntimeDurableReadiness(payload: Record = {}, options: { requirePostgresAdapter?: boolean } = {}) { const runtime = record(payload.runtime); const readiness = record(payload.readiness); const durability = record(readiness.durability); const db = record(payload.db); const dbRuntimeReadiness = record(db.runtimeReadiness); const gates = record(runtime.gates ?? durability.gates); const gateBlocker = firstBlockedRuntimeGate(gates); const queryResult = firstNonEmpty(record(runtime.connection).queryResult, durability.queryResult, dbRuntimeReadiness.queryResult); const queryBlocker = runtimeDurableBlockerFromQueryResult(queryResult); const readyQueryResult = isRuntimeDurableReadyQueryResult(queryResult); const explicitDurabilityReady = durabilityReadySignal({ runtime, durability, dbRuntimeReadiness, gateBlocker, queryResult, readyQueryResult, queryBlocker }); const directBlocker = firstNonEmpty( explicitDurabilityReady ? "" : runtime.blocker, durability.blocker, dbRuntimeReadiness.blocker, explicitDurabilityReady ? "" : blockerCodeStartingWith(payload, "runtime_durable_adapter_") ); const reasonCode = firstNonEmpty(directBlocker, gateBlocker?.blocker, queryBlocker); const blockedLayer = firstNonEmpty( record(runtime.durabilityContract).blockedLayer, durability.blockedLayer, dbRuntimeReadiness.blockedLayer, gateBlocker?.layer, runtimeDurableLayerFromBlocker(reasonCode), runtimeDurableLayerFromQueryResult(queryResult) ); const adapter = firstNonEmpty(runtime.adapter, durability.adapter, dbRuntimeReadiness.adapter, "unknown"); const runtimeDurable = runtime.durable === true || durability.durable === true || dbRuntimeReadiness.durable === true; const runtimeExplicitlyNonDurable = runtime.durable === false || durability.durable === false || dbRuntimeReadiness.durable === false; const runtimeReady = runtime.ready === true || durability.runtimeReady === true || dbRuntimeReadiness.ready === true; const durabilityReady = durability.ready === true || dbRuntimeReadiness.ready === true || runtimeReady; const liveRuntimeEvidence = runtime.liveRuntimeEvidence === true || durability.liveRuntimeEvidence === true || dbRuntimeReadiness.liveRuntimeEvidence === true; const rawStatus = rawStatusFrom(payload); const postgresRequired = options.requirePostgresAdapter === true; const ready = (!postgresRequired || adapter === "postgres") && runtimeDurable && runtimeReady && durabilityReady && liveRuntimeEvidence && !reasonCode && !blockedLayer && (!queryResult || readyQueryResult); if (ready) { return { status: "ready", ready: true, classification: "runtime_durable_ready", reasonCode: "runtime_durable_ready", reason: `runtime durable ready; adapter=${adapter}; queryResult=${queryResult || "not_observed"}`, impact: "HWPOD runtime readiness checks may rely on durable runtime evidence when persistence readiness is green.", safeNextAction: "Continue with repo-owned DEV CD postflight; do not read or print Secret values.", retryable: true, readonly: false, evidence: runtimeDurableEvidence({ adapter, runtimeDurable, runtimeReady, durabilityReady, liveRuntimeEvidence, blockedLayer: null, queryResult, readyQueryResult, reasonCode: null, gates, rawStatus, postgresRequired }) }; } const nonReadyReasonCode = firstNonEmpty( reasonCode, postgresRequired && adapter !== "postgres" ? "runtime_durable_adapter_not_postgres" : "", runtimeExplicitlyNonDurable ? "runtime_durable_false" : "", runtimeDurable ? "" : "runtime_durable_adapter_missing", runtimeReady ? "" : "runtime_durable_runtime_not_ready", liveRuntimeEvidence ? "" : "runtime_durable_live_evidence_missing", blockedRuntimeStatus(rawStatus) ? rawStatus : "", "runtime_durable_adapter_not_ready" ); const layer = firstNonEmpty(blockedLayer, runtimeDurableLayerFromBlocker(nonReadyReasonCode), runtimeExplicitlyNonDurable ? "runtime-durable" : "", "runtime-durable"); return { status: "blocked", ready: false, classification: nonReadyReasonCode, reasonCode: nonReadyReasonCode, reason: runtimeDurableBlockedReason({ reasonCode: nonReadyReasonCode, adapter, layer, queryResult }), impact: "HWPOD runtime readiness remains source-level while durable runtime evidence is blocked.", safeNextAction: runtimeDurableSafeNextAction(nonReadyReasonCode, layer), retryable: true, readonly: true, evidence: runtimeDurableEvidence({ adapter, runtimeDurable, runtimeReady, durabilityReady, liveRuntimeEvidence, blockedLayer: layer, queryResult, readyQueryResult, reasonCode: nonReadyReasonCode, gates, rawStatus, postgresRequired }) }; } function durabilityReadySignal(input: { runtime: Record; durability: Record; dbRuntimeReadiness: Record; gateBlocker: { layer: string; blocker: string } | null; queryResult: string; readyQueryResult: boolean; queryBlocker: string }): boolean { if (input.gateBlocker || input.queryBlocker) return false; if (input.queryResult && !input.readyQueryResult) return false; const ready = input.durability.ready === true || input.dbRuntimeReadiness.ready === true || record(input.runtime.durabilityContract).ready === true; if (!ready) return false; return !firstNonEmpty(input.durability.blocker, input.dbRuntimeReadiness.blocker, record(input.runtime.durabilityContract).blocker, input.durability.blockedLayer, input.dbRuntimeReadiness.blockedLayer, record(input.runtime.durabilityContract).blockedLayer); } function firstBlockedRuntimeGate(gates: Record): { layer: string; blocker: string } | null { for (const layer of gateLayers) { const gate = record(gates[layer]); const status = normalized(gate.status); if (gate.ready === false || status === "blocked" || status === "failed") return { layer, blocker: firstNonEmpty(gate.blocker, blockerForLayer(layer)) }; } return null; } function blockerForLayer(layer: string): string { if (layer === "ssl") return "runtime_durable_adapter_ssl_blocked"; if (layer === "auth") return "runtime_durable_adapter_auth_blocked"; if (layer === "schema") return "runtime_durable_adapter_schema_blocked"; if (layer === "migration") return "runtime_durable_adapter_migration_blocked"; return "runtime_durable_adapter_query_blocked"; } function runtimeDurableLayerFromBlocker(blocker: string): string { const text = normalized(blocker); if (text.includes("ssl")) return "ssl"; if (text.includes("auth")) return "auth"; if (text.includes("schema")) return "schema"; if (text.includes("migration")) return "migration"; if (text.includes("query")) return "durability_query"; if (text.includes("missing") || text.includes("unconfigured") || text.includes("driver")) return "adapter"; return ""; } function runtimeDurableLayerFromQueryResult(queryResult: string): string { const text = normalized(queryResult); if (!text || isRuntimeDurableReadyQueryResult(text)) return ""; if (text.includes("ssl")) return "ssl"; if (text.includes("auth")) return "auth"; if (text.includes("schema")) return "schema"; if (text.includes("migration")) return "migration"; if (text.includes("query")) return "durability_query"; if (text.includes("driver")) return "adapter"; return ""; } function runtimeDurableBlockerFromQueryResult(queryResult: string): string { const text = normalized(queryResult); if (!text || isRuntimeDurableReadyQueryResult(text)) return ""; return blockedQueryResults[text] ?? ""; } function isRuntimeDurableReadyQueryResult(queryResult: string): boolean { return readyQueryResults.includes(normalized(queryResult)); } function runtimeDurableEvidence(input: { adapter: string; runtimeDurable: boolean; runtimeReady: boolean; durabilityReady: boolean; liveRuntimeEvidence: boolean; blockedLayer: string | null; queryResult: string; readyQueryResult: boolean; reasonCode: string | null; gates: Record; rawStatus: unknown; postgresRequired: boolean }) { return { adapter: input.adapter, durable: input.runtimeDurable, runtimeReady: input.runtimeReady, durabilityReady: input.durabilityReady, liveRuntimeEvidence: input.liveRuntimeEvidence, blocker: input.reasonCode, blockedLayer: input.blockedLayer, queryResult: input.queryResult || null, readyQueryResult: input.readyQueryResult, rawStatus: input.rawStatus || null, postgresRequired: input.postgresRequired, gates: Object.fromEntries(gateLayers.map((layer) => { const gate = record(input.gates[layer]); return [layer, { checked: gate.checked === true, ready: gate.ready === true, status: gate.status ?? "unknown", blocker: gate.blocker ?? null }]; })), secretMaterialRead: false }; } function runtimeDurableBlockedReason(input: { reasonCode: string; adapter: string; layer: string; queryResult: string }): string { if (input.reasonCode === "runtime_durable_false") return `runtime durable=false; adapter=${input.adapter}; queryResult=${input.queryResult || "not_observed"}`; if (input.reasonCode === "runtime_durable_adapter_not_postgres") return `runtime durable adapter is not postgres; adapter=${input.adapter}; queryResult=${input.queryResult || "not_observed"}`; return `runtime durable blocked at ${input.layer || "runtime-durable"}; blocker=${input.reasonCode}; queryResult=${input.queryResult || "unknown"}`; } function runtimeDurableSafeNextAction(reasonCode: string, layer: string): string { const text = normalized(`${reasonCode} ${layer}`); if (text.includes("auth")) return "Repair DEV DB auth/SecretRef metadata through repo-owned provisioning, then rerun postflight without printing Secret values."; if (text.includes("schema") || text.includes("migration")) return "Run repo-owned DEV runtime provisioning and migration, then rerun postflight."; if (text.includes("ssl")) return "Align DEV DB SSL mode with the repo-owned DEV contract, then rerun postflight."; if (text.includes("query")) return "Inspect durable runtime query readiness, preserve evidence persistence, and rerun postflight."; if (text.includes("postgres")) return "Run DEV with HWLAB_CLOUD_RUNTIME_ADAPTER=postgres and DB URLs injected only through SecretRefs."; return "Restore durable runtime readiness through repo-owned DEV CD steps, then rerun postflight."; } function rawStatusFrom(payload: Record): unknown { return payload.status ?? record(payload.readiness).status ?? record(payload.runtime).status ?? record(record(payload.readiness).durability).status ?? ""; } function blockerCodeStartingWith(payload: Record, prefix: string): string { const readiness = record(payload.readiness); return [...array(payload.blockerCodes), ...array(readiness.blockerCodes)].map(String).find((code) => code.startsWith(prefix)) ?? ""; } function blockedRuntimeStatus(value: unknown): boolean { return ["error", "failed", "failure", "blocked", "unavailable", "rejected", "degraded"].includes(normalized(value)); } function record(value: unknown): Record { return value && typeof value === "object" ? value as Record : {}; } function array(value: unknown): unknown[] { return Array.isArray(value) ? value : []; } function firstNonEmpty(...values: unknown[]): string { for (const value of values) { const text = String(value ?? "").trim(); if (text) return text; } return ""; } function normalized(value: unknown): string { return String(value ?? "").trim().toLowerCase(); }