Files
pikasTech-HWLAB/tools/src/runtime-durable-readiness.ts
T
2026-06-05 17:23:56 +08:00

217 lines
12 KiB
TypeScript

const readyQueryResults = Object.freeze(["durable_readiness_ready", "runtime_durable_ready", "query_ready", "readiness_ready", "ready", "ok", "pass", "passed"]);
const blockedQueryResults = Object.freeze<Record<string, string>>({
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<string, unknown> = {}, 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<string, unknown>; durability: Record<string, unknown>; dbRuntimeReadiness: Record<string, unknown>; 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<string, unknown>): { 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<string, unknown>; 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<string, unknown>): unknown {
return payload.status ?? record(payload.readiness).status ?? record(payload.runtime).status ?? record(record(payload.readiness).durability).status ?? "";
}
function blockerCodeStartingWith(payload: Record<string, unknown>, 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<string, unknown> {
return value && typeof value === "object" ? value as Record<string, unknown> : {};
}
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();
}