Files
pikasTech-HWLAB/internal/cloud/health-contract.mjs
T
2026-05-23 03:13:09 +00:00

208 lines
7.8 KiB
JavaScript

import {
RUNTIME_DURABLE_ADAPTER_MISSING,
RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED,
RUNTIME_DURABLE_ADAPTER_DRIVER_MISSING,
RUNTIME_DURABLE_ADAPTER_MIGRATION_BLOCKED,
RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED,
RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED,
RUNTIME_DURABLE_ADAPTER_SSL_BLOCKED,
RUNTIME_DURABLE_ADAPTER_UNCONFIGURED,
RUNTIME_DURABILITY_REQUIRED_EVIDENCE,
RUNTIME_STORE_KIND_POSTGRES
} from "../db/runtime-store.mjs";
export const CLOUD_API_HEALTH_CONTRACT_VERSION = "v3";
export function buildCloudApiReadiness({ db, codeAgent, runtime } = {}) {
const dbReady = isDbReady(db);
const codeAgentReady = isCodeAgentReady(codeAgent);
const runtimeReady = isRuntimeReady(runtime);
const blockers = [];
let runtimeReadinessBlocker = null;
if (!dbReady) {
blockers.push(dbBlocker(db));
}
if (!codeAgentReady) {
blockers.push(codeAgentBlocker(codeAgent));
}
if (!runtimeReady) {
runtimeReadinessBlocker = runtimeBlocker(runtime);
blockers.push(runtimeReadinessBlocker);
}
return {
contractVersion: CLOUD_API_HEALTH_CONTRACT_VERSION,
ready: blockers.length === 0,
status: blockers.length === 0 ? "ok" : "degraded",
blockerCodes: blockers.map((blocker) => blocker.code),
blockers,
components: {
db: dbReady ? "ready" : "blocked",
codeAgent: codeAgentReady ? "ready" : "blocked",
runtime: runtimeReady ? "ready" : dbReady ? "blocked" : "waiting_for_db"
},
durability: buildRuntimeDurabilityReadiness({
db,
runtime,
runtimeReady,
runtimeBlocker: runtimeReadinessBlocker
})
};
}
function isDbReady(db) {
return Boolean(db?.ready && db?.connected && db?.liveConnected !== false && db?.liveDbEvidence);
}
function isCodeAgentReady(codeAgent) {
return Boolean(codeAgent?.ready && codeAgent?.status !== "blocked");
}
function isRuntimeReady(runtime) {
if (runtime?.durable !== true) {
return false;
}
if (runtime?.ready !== true || runtime?.liveRuntimeEvidence !== true) {
return false;
}
return !["blocked", "degraded", "failed"].includes(runtime.status);
}
function dbBlocker(db) {
return {
code: db?.configReady ? "db_live_connection_blocked" : "db_runtime_config_blocked",
type: "runtime_blocker",
scope: "cloud-api-db",
status: "open",
sourceIssue: "pikasTech/HWLAB#49",
summary: db?.blocker ?? "cloud-api DB live readiness is not established",
evidence: {
configReady: Boolean(db?.configReady),
connected: Boolean(db?.connected),
liveConnected: Boolean(db?.liveConnected),
liveDbEvidence: Boolean(db?.liveDbEvidence),
connectionAttempted: Boolean(db?.connectionAttempted ?? db?.connectionChecked),
connectionResult: db?.connectionResult ?? db?.connection?.result ?? "unknown"
}
};
}
function codeAgentBlocker(codeAgent) {
return {
code: codeAgent?.reason ?? "code_agent_provider_unavailable",
type: "agent_blocker",
scope: "code-agent-provider",
status: "open",
sourceIssue: "pikasTech/HWLAB#143",
summary: codeAgent?.summary ?? "Code Agent provider readiness is not established",
evidence: {
provider: codeAgent?.provider ?? "unknown",
backend: codeAgent?.backend ?? "unknown",
missingEnv: Array.isArray(codeAgent?.missingEnv) ? [...codeAgent.missingEnv] : [],
secretsRedacted: true
}
};
}
function runtimeBlocker(runtime) {
const durable = runtime?.durable === true;
const durableRequested = runtime?.durableRequested === true || runtime?.adapter === RUNTIME_STORE_KIND_POSTGRES;
const unhealthy = (durable || durableRequested) && ["blocked", "degraded", "failed"].includes(runtime?.status);
const blocker = runtime?.blocker;
const blockedLayer = classifyRuntimeDurabilityLayer(runtime);
return {
code: durableRequested && unhealthy ? blocker ?? "runtime_durable_adapter_unhealthy" : RUNTIME_DURABLE_ADAPTER_MISSING,
type: "runtime_blocker",
scope: "runtime-durable-adapter",
status: "open",
sourceIssue: "pikasTech/HWLAB#164",
summary: durableRequested && unhealthy
? runtime?.reason ?? "cloud-api durable runtime adapter is configured but not ready"
: "cloud-api DB is live, but L1 runtime writes still use process-local memory because no DB-backed durable adapter is configured",
evidence: {
adapter: runtime?.adapter ?? "unknown",
durable: Boolean(runtime?.durable),
durableRequested: Boolean(runtime?.durableRequested),
durableCapable: Boolean(runtime?.durableCapable),
runtimeStatus: runtime?.status ?? "unknown",
runtimeReady: Boolean(runtime?.ready),
liveRuntimeEvidence: Boolean(runtime?.liveRuntimeEvidence),
schemaReady: Boolean(runtime?.schema?.ready),
schemaChecked: Boolean(runtime?.schema?.checked),
migrationReady: Boolean(runtime?.migration?.ready),
migrationChecked: Boolean(runtime?.migration?.checked),
gates: runtime?.gates ?? null,
blockedLayer,
queryAttempted: Boolean(runtime?.connection?.queryAttempted),
queryResult: runtime?.connection?.queryResult ?? "unknown",
dbBackedAdapterRequired: true,
dbLiveEvidenceIsDurabilityEvidence: false,
requiredEvidence: runtime?.durabilityContract?.requiredEvidence ?? RUNTIME_DURABILITY_REQUIRED_EVIDENCE,
secretMaterialRead: false
},
nextTask: durableRequested
? "Apply/repair the runtime DB schema or DB auth, then rerun /health/live without printing DB secret material."
: "Set HWLAB_CLOUD_RUNTIME_ADAPTER=postgres after the runtime DB schema is available; keep HWLAB_CLOUD_DB_URL injected from Secret."
};
}
function buildRuntimeDurabilityReadiness({ db, runtime, runtimeReady, runtimeBlocker } = {}) {
const dbLiveEvidenceObserved = Boolean(db?.ready && db?.connected && db?.liveConnected !== false && db?.liveDbEvidence);
const contract = runtime?.durabilityContract ?? {};
return {
status: runtimeReady ? "ready" : "blocked",
ready: runtimeReady,
adapter: runtime?.adapter ?? "unknown",
durable: Boolean(runtime?.durable),
durableRequested: Boolean(runtime?.durableRequested || runtime?.adapter === RUNTIME_STORE_KIND_POSTGRES),
durableCapable: Boolean(runtime?.durableCapable),
runtimeStatus: runtime?.status ?? "unknown",
runtimeReady: Boolean(runtime?.ready),
liveRuntimeEvidence: Boolean(runtime?.liveRuntimeEvidence),
dbLiveEvidenceObserved,
dbLiveEvidenceIsDurabilityEvidence: false,
requiredEvidence: contract.requiredEvidence ?? RUNTIME_DURABILITY_REQUIRED_EVIDENCE,
blockedLayer: runtimeReady ? null : contract.blockedLayer ?? classifyRuntimeDurabilityLayer(runtime),
blocker: runtimeReady ? null : runtimeBlocker?.code ?? runtime?.blocker ?? RUNTIME_DURABLE_ADAPTER_MISSING,
gates: runtime?.gates ?? null,
queryAttempted: Boolean(runtime?.connection?.queryAttempted),
queryResult: runtime?.connection?.queryResult ?? "unknown",
secretMaterialRead: false
};
}
function classifyRuntimeDurabilityLayer(runtime = {}) {
const contractLayer = runtime?.durabilityContract?.blockedLayer;
if (typeof contractLayer === "string" && contractLayer.length > 0) {
return contractLayer;
}
const blocker = runtime?.blocker;
if (
blocker === RUNTIME_DURABLE_ADAPTER_MISSING ||
blocker === RUNTIME_DURABLE_ADAPTER_UNCONFIGURED ||
blocker === RUNTIME_DURABLE_ADAPTER_DRIVER_MISSING
) {
return "adapter";
}
if (blocker === RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED) {
return "auth";
}
if (blocker === RUNTIME_DURABLE_ADAPTER_SSL_BLOCKED) {
return "ssl";
}
if (blocker === RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED) {
return "schema";
}
if (blocker === RUNTIME_DURABLE_ADAPTER_MIGRATION_BLOCKED) {
return "migration";
}
if (blocker === RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED || runtime?.connection?.queryAttempted) {
return "durability_query";
}
return "adapter";
}