Merge pull request #211 from pikasTech/fix/runtime-durability-contract-164

fix: clarify runtime durability health contract
This commit is contained in:
Lyon
2026-05-23 08:56:49 +08:00
committed by GitHub
6 changed files with 357 additions and 11 deletions
+8
View File
@@ -177,6 +177,14 @@ non-durable until the schema, migration ledger, and read readiness gates are
proven through the configured adapter. Documenting or testing this contract
must not mutate runtime state or PROD.
`/health` and `/health/live` expose this split in `readiness.durability`.
`dbLiveEvidenceObserved=true` with
`dbLiveEvidenceIsDurabilityEvidence=false` means the DB TCP/live layer is
separate from runtime persistence. If runtime durability is blocked,
`blockedLayer` names the current adapter/auth/schema/migration/durability query
layer, and `requiredEvidence` remains the durable adapter schema, migration,
and read query contract.
## Runtime Migration Automation
The repo-owned runtime migration entrypoint is:
+76 -3
View File
@@ -1,15 +1,23 @@
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_UNCONFIGURED,
RUNTIME_DURABILITY_REQUIRED_EVIDENCE,
RUNTIME_STORE_KIND_POSTGRES
} from "../db/runtime-store.mjs";
export const CLOUD_API_HEALTH_CONTRACT_VERSION = "v2";
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));
@@ -20,7 +28,8 @@ export function buildCloudApiReadiness({ db, codeAgent, runtime } = {}) {
}
if (!runtimeReady) {
blockers.push(runtimeBlocker(runtime));
runtimeReadinessBlocker = runtimeBlocker(runtime);
blockers.push(runtimeReadinessBlocker);
}
return {
@@ -33,7 +42,13 @@ export function buildCloudApiReadiness({ db, codeAgent, runtime } = {}) {
db: dbReady ? "ready" : "blocked",
codeAgent: codeAgentReady ? "ready" : "blocked",
runtime: runtimeReady ? "ready" : dbReady ? "blocked" : "waiting_for_db"
}
},
durability: buildRuntimeDurabilityReadiness({
db,
runtime,
runtimeReady,
runtimeBlocker: runtimeReadinessBlocker
})
};
}
@@ -96,6 +111,7 @@ function runtimeBlocker(runtime) {
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",
@@ -118,9 +134,12 @@ function runtimeBlocker(runtime) {
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
@@ -128,3 +147,57 @@ function runtimeBlocker(runtime) {
: "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_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";
}
+6
View File
@@ -72,6 +72,12 @@ test("system.health includes the redacted DEV DB env contract", async () => {
assert.equal(response.result.db.redaction.secretMaterialRead, false);
assert.equal(response.result.db.safety.liveDbEvidence, false);
assert.equal(response.result.runtime.durable, false);
assert.equal(response.result.runtime.durabilityContract.dbLiveEvidenceIsDurabilityEvidence, false);
assert.equal(response.result.readiness.contractVersion, "v3");
assert.equal(response.result.readiness.durability.status, "blocked");
assert.equal(response.result.readiness.durability.dbLiveEvidenceObserved, false);
assert.equal(response.result.readiness.durability.dbLiveEvidenceIsDurabilityEvidence, false);
assert.equal(response.result.readiness.durability.blockedLayer, "adapter");
assert.ok(response.result.db.missingEnv.includes("HWLAB_CLOUD_DB_URL"));
assert.equal(response.result.db.secretRefs[0].secretName, "hwlab-cloud-api-dev-db");
assert.equal(response.result.db.secretRefs[0].secretKey, "database-url");
+130 -1
View File
@@ -4,7 +4,10 @@ import { createServer as createTcpServer } from "node:net";
import test from "node:test";
import { createCloudApiServer } from "./server.mjs";
import { RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED } from "../db/runtime-store.mjs";
import {
RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED,
RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED
} from "../db/runtime-store.mjs";
test("cloud api exposes /health, /health/live, and /live probes", async () => {
const originalUrl = process.env.HWLAB_CLOUD_DB_URL;
@@ -51,6 +54,13 @@ test("cloud api exposes /health, /health/live, and /live probes", async () => {
assert.equal(healthPayload.db.safety.liveDbEvidence, false);
assert.equal(healthPayload.runtime.durable, false);
assert.equal(healthPayload.runtime.status, "degraded");
assert.equal(healthPayload.runtime.durabilityContract.dbLiveEvidenceIsDurabilityEvidence, false);
assert.equal(healthPayload.runtime.durabilityContract.blockedLayer, "adapter");
assert.equal(healthPayload.readiness.contractVersion, "v3");
assert.equal(healthPayload.readiness.durability.status, "blocked");
assert.equal(healthPayload.readiness.durability.dbLiveEvidenceObserved, false);
assert.equal(healthPayload.readiness.durability.dbLiveEvidenceIsDurabilityEvidence, false);
assert.equal(healthPayload.readiness.durability.blockedLayer, "adapter");
assert.equal(healthPayload.codeAgent.status, "blocked");
assert.equal(healthPayload.codeAgent.blocker, "凭证缺口");
assert.equal(healthPayload.codeAgent.reason, "provider_unavailable");
@@ -150,6 +160,15 @@ test("cloud api health reports DB env presence and live connection classificatio
assert.equal(payload.runtime.durable, false);
assert.equal(payload.runtime.status, "degraded");
assert.match(payload.runtime.reason, /process-local/u);
assert.equal(payload.runtime.durabilityContract.blockedLayer, "adapter");
assert.equal(payload.runtime.durabilityContract.dbLiveEvidenceIsDurabilityEvidence, false);
assert.equal(payload.readiness.components.db, "ready");
assert.equal(payload.readiness.components.runtime, "blocked");
assert.equal(payload.readiness.durability.status, "blocked");
assert.equal(payload.readiness.durability.dbLiveEvidenceObserved, true);
assert.equal(payload.readiness.durability.dbLiveEvidenceIsDurabilityEvidence, false);
assert.equal(payload.readiness.durability.blockedLayer, "adapter");
assert.equal(payload.readiness.durability.liveRuntimeEvidence, false);
assert.equal(payload.db.redaction.valuesRedacted, true);
assert.equal(payload.db.redaction.endpointRedacted, true);
assert.equal(payload.db.redaction.secretMaterialRead, false);
@@ -248,6 +267,12 @@ test("cloud api health separates DB connected from durable runtime schema readin
assert.equal(payload.runtime.blocker, RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED);
assert.equal(payload.readiness.components.db, "ready");
assert.equal(payload.readiness.components.runtime, "blocked");
assert.equal(payload.readiness.durability.status, "blocked");
assert.equal(payload.readiness.durability.dbLiveEvidenceObserved, true);
assert.equal(payload.readiness.durability.dbLiveEvidenceIsDurabilityEvidence, false);
assert.equal(payload.readiness.durability.blockedLayer, "schema");
assert.equal(payload.readiness.durability.queryAttempted, true);
assert.equal(payload.readiness.durability.queryResult, "schema_blocked");
assert.ok(payload.blockerCodes.includes(RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED));
assert.equal(JSON.stringify(payload).includes("password"), false);
} finally {
@@ -270,6 +295,110 @@ test("cloud api health separates DB connected from durable runtime schema readin
}
});
test("cloud api health keeps DB live evidence separate when durable adapter query is blocked", async () => {
const originalUrl = process.env.HWLAB_CLOUD_DB_URL;
const originalSslMode = process.env.HWLAB_CLOUD_DB_SSL_MODE;
const fakeDb = createTcpServer((socket) => socket.end());
await new Promise((resolve) => fakeDb.listen(0, "127.0.0.1", resolve));
const dbPort = fakeDb.address().port;
process.env.HWLAB_CLOUD_DB_URL = `postgres://hwlab_test:password@127.0.0.1:${dbPort}/hwlab`;
process.env.HWLAB_CLOUD_DB_SSL_MODE = "require";
const server = createCloudApiServer({
runtimeStore: {
async readiness() {
return {
adapter: "postgres",
durable: false,
durableRequested: true,
durableCapable: false,
ready: false,
status: "blocked",
blocker: RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED,
reason: "test runtime durable read query is blocked",
liveRuntimeEvidence: false,
fixtureEvidence: false,
connection: {
queryAttempted: true,
queryResult: "query_blocked",
endpointRedacted: true,
valueRedacted: true,
errorCode: "57014"
},
schema: {
checked: true,
ready: true,
missingTables: [],
missingColumns: []
},
migration: {
checked: true,
ready: true,
missing: false
},
gates: {
auth: { checked: true, ready: true, status: "ready", blocker: null },
schema: { checked: true, ready: true, status: "ready", blocker: null },
migration: { checked: true, ready: true, status: "ready", blocker: null },
durability: {
checked: true,
ready: false,
status: "blocked",
blocker: RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED
}
},
counts: {}
};
}
}
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const { port } = server.address();
const response = await fetch(`http://127.0.0.1:${port}/health/live`);
const payload = await response.json();
assert.equal(response.status, 200);
assert.equal(payload.status, "degraded");
assert.equal(payload.db.ready, true);
assert.equal(payload.db.connected, true);
assert.equal(payload.db.liveDbEvidence, true);
assert.equal(payload.runtime.durable, false);
assert.equal(payload.runtime.ready, false);
assert.equal(payload.runtime.blocker, RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED);
assert.equal(payload.readiness.components.db, "ready");
assert.equal(payload.readiness.components.runtime, "blocked");
assert.equal(payload.readiness.durability.status, "blocked");
assert.equal(payload.readiness.durability.dbLiveEvidenceObserved, true);
assert.equal(payload.readiness.durability.dbLiveEvidenceIsDurabilityEvidence, false);
assert.equal(payload.readiness.durability.blockedLayer, "durability_query");
assert.equal(payload.readiness.durability.queryAttempted, true);
assert.equal(payload.readiness.durability.queryResult, "query_blocked");
assert.equal(payload.readiness.durability.liveRuntimeEvidence, false);
assert.ok(payload.blockerCodes.includes(RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED));
assert.equal(JSON.stringify(payload).includes("password"), false);
assert.equal(JSON.stringify(payload).includes("127.0.0.1"), false);
assert.equal(JSON.stringify(payload).includes(String(dbPort)), false);
} finally {
if (originalUrl === undefined) {
delete process.env.HWLAB_CLOUD_DB_URL;
} else {
process.env.HWLAB_CLOUD_DB_URL = originalUrl;
}
if (originalSslMode === undefined) {
delete process.env.HWLAB_CLOUD_DB_SSL_MODE;
} else {
process.env.HWLAB_CLOUD_DB_SSL_MODE = originalSslMode;
}
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
await new Promise((resolve, reject) => {
fakeDb.close((error) => (error ? reject(error) : resolve()));
});
}
});
test("cloud api health does not treat DB env presence-only as live readiness", async () => {
const originalUrl = process.env.HWLAB_CLOUD_DB_URL;
const originalSslMode = process.env.HWLAB_CLOUD_DB_SSL_MODE;
+50 -6
View File
@@ -27,6 +27,7 @@ export const RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED = "runtime_durable_adapter_aut
export const RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED = "runtime_durable_adapter_schema_blocked";
export const RUNTIME_DURABLE_ADAPTER_MIGRATION_BLOCKED = "runtime_durable_adapter_migration_blocked";
export const RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED = "runtime_durable_adapter_query_blocked";
export const RUNTIME_DURABILITY_REQUIRED_EVIDENCE = "runtime_adapter_schema_migration_read_query";
export const RUNTIME_ADAPTER_ENV = "HWLAB_CLOUD_RUNTIME_ADAPTER";
export const RUNTIME_DURABLE_ENV = "HWLAB_CLOUD_RUNTIME_DURABLE";
@@ -78,7 +79,7 @@ export class CloudRuntimeStore {
}
summary() {
return {
return addRuntimeDurabilityContract({
adapter: RUNTIME_STORE_KIND,
durable: false,
status: "degraded",
@@ -97,7 +98,7 @@ export class CloudRuntimeStore {
auditEvents: this.auditEvents.size,
evidenceRecords: this.evidenceRecords.size
}
};
});
}
registerGatewaySession(params = {}, requestMeta = {}) {
@@ -719,7 +720,7 @@ export class PostgresCloudRuntimeStore {
return this.summary();
}
this.lastReadiness = {
this.lastReadiness = addRuntimeDurabilityContract({
adapter: RUNTIME_STORE_KIND_POSTGRES,
durable: true,
durableRequested: true,
@@ -747,7 +748,7 @@ export class PostgresCloudRuntimeStore {
safety: runtimeSafety(),
adapterContract: postgresAdapterContract(),
counts
};
});
return this.summary();
}
@@ -1113,7 +1114,7 @@ export class PostgresCloudRuntimeStore {
missingColumns: []
};
const blockedMigration = migration ?? notCheckedRuntimeMigration();
return {
return addRuntimeDurabilityContract({
adapter: RUNTIME_STORE_KIND_POSTGRES,
durable: false,
durableRequested: true,
@@ -1145,7 +1146,7 @@ export class PostgresCloudRuntimeStore {
}),
safety: runtimeSafety(),
adapterContract: postgresAdapterContract()
};
});
}
}
@@ -1283,6 +1284,49 @@ function postgresAdapterContract() {
};
}
function addRuntimeDurabilityContract(summary) {
return {
...summary,
durabilityContract: {
ready: summary.durable === true && summary.ready === true && summary.liveRuntimeEvidence === true,
status: summary.durable === true && summary.ready === true && summary.liveRuntimeEvidence === true ? "ready" : "blocked",
requiredEvidence: RUNTIME_DURABILITY_REQUIRED_EVIDENCE,
dbLiveEvidenceIsDurabilityEvidence: false,
liveRuntimeEvidence: Boolean(summary.liveRuntimeEvidence),
adapterQueryRequired: true,
blockedLayer: summary.durable === true && summary.ready === true && summary.liveRuntimeEvidence === true
? null
: durabilityBlockedLayer(summary),
blocker: summary.blocker ?? null,
secretMaterialRead: false
}
};
}
function durabilityBlockedLayer(summary = {}) {
const blocker = summary.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_SCHEMA_BLOCKED) {
return "schema";
}
if (blocker === RUNTIME_DURABLE_ADAPTER_MIGRATION_BLOCKED) {
return "migration";
}
if (summary.connection?.queryAttempted || blocker === RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED) {
return "durability_query";
}
return "adapter";
}
function runtimeSafety() {
return {
devOnly: true,
+87 -1
View File
@@ -4,7 +4,9 @@ import test from "node:test";
import { handleJsonRpcRequest } from "../cloud/json-rpc.mjs";
import { validateResponse } from "../protocol/index.mjs";
import {
RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED,
RUNTIME_DURABLE_ADAPTER_MIGRATION_BLOCKED,
RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED,
RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED,
RUNTIME_STORE_KIND_POSTGRES,
createCloudRuntimeStore,
@@ -25,6 +27,45 @@ test("memory runtime store is never marked durable", () => {
assert.equal(summary.status, "degraded");
assert.equal(summary.ready, undefined);
assert.equal(summary.adapterContract.secretMaterialRequiredInSource, false);
assert.equal(summary.durabilityContract.ready, false);
assert.equal(summary.durabilityContract.status, "blocked");
assert.equal(summary.durabilityContract.blockedLayer, "adapter");
assert.equal(summary.durabilityContract.adapterQueryRequired, true);
assert.equal(summary.durabilityContract.dbLiveEvidenceIsDurabilityEvidence, false);
assert.equal(summary.durabilityContract.secretMaterialRead, false);
});
test("configured postgres runtime classifies auth blocker before schema and migration", async () => {
const store = createConfiguredCloudRuntimeStore({
env: {
HWLAB_CLOUD_RUNTIME_ADAPTER: "postgres",
HWLAB_CLOUD_RUNTIME_DURABLE: "true"
},
dbUrl: "postgres://hwlab_redacted@db.example.invalid:5432/hwlab",
queryClient: {
async query(sql) {
assert.match(sql, /information_schema\.columns/u);
const error = new Error("auth failed");
error.code = "28P01";
throw error;
}
}
});
const readiness = await store.readiness();
assert.equal(readiness.adapter, RUNTIME_STORE_KIND_POSTGRES);
assert.equal(readiness.durable, false);
assert.equal(readiness.ready, false);
assert.equal(readiness.status, "blocked");
assert.equal(readiness.blocker, RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED);
assert.equal(readiness.connection.queryAttempted, true);
assert.equal(readiness.connection.queryResult, "auth_blocked");
assert.equal(readiness.gates.auth.status, "blocked");
assert.equal(readiness.gates.schema.status, "not_checked");
assert.equal(readiness.gates.migration.status, "not_checked");
assert.equal(readiness.durabilityContract.ready, false);
assert.equal(readiness.durabilityContract.blockedLayer, "auth");
assert.equal(readiness.durabilityContract.dbLiveEvidenceIsDurabilityEvidence, false);
});
test("configured postgres runtime reports schema blocker without green readiness", async () => {
@@ -56,6 +97,9 @@ test("configured postgres runtime reports schema blocker without green readiness
assert.equal(readiness.migration.checked, false);
assert.equal(readiness.gates.schema.status, "blocked");
assert.equal(readiness.gates.migration.status, "not_checked");
assert.equal(readiness.durabilityContract.ready, false);
assert.equal(readiness.durabilityContract.blockedLayer, "schema");
assert.equal(readiness.durabilityContract.dbLiveEvidenceIsDurabilityEvidence, false);
assert.equal(JSON.stringify(readiness).includes("redacted@db.example"), false);
});
@@ -83,6 +127,38 @@ test("configured postgres runtime keeps durable false when migration ledger is m
assert.equal(readiness.gates.schema.status, "ready");
assert.equal(readiness.gates.migration.status, "blocked");
assert.equal(readiness.liveRuntimeEvidence, false);
assert.equal(readiness.durabilityContract.ready, false);
assert.equal(readiness.durabilityContract.blockedLayer, "migration");
assert.equal(readiness.durabilityContract.dbLiveEvidenceIsDurabilityEvidence, false);
});
test("configured postgres runtime classifies durable read query blocker after schema and migration", async () => {
const store = createConfiguredCloudRuntimeStore({
env: {
HWLAB_CLOUD_RUNTIME_ADAPTER: "postgres",
HWLAB_CLOUD_RUNTIME_DURABLE: "true"
},
dbUrl: "postgres://hwlab_redacted@db.example.invalid:5432/hwlab",
queryClient: createFakePostgresClient({ migrationReady: true, countErrorCode: "57014" })
});
const readiness = await store.readiness();
assert.equal(readiness.adapter, RUNTIME_STORE_KIND_POSTGRES);
assert.equal(readiness.durable, false);
assert.equal(readiness.ready, false);
assert.equal(readiness.status, "blocked");
assert.equal(readiness.blocker, RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED);
assert.equal(readiness.schema.ready, true);
assert.equal(readiness.migration.ready, true);
assert.equal(readiness.connection.queryAttempted, true);
assert.equal(readiness.connection.queryResult, "query_blocked");
assert.equal(readiness.gates.auth.status, "ready");
assert.equal(readiness.gates.schema.status, "ready");
assert.equal(readiness.gates.migration.status, "ready");
assert.equal(readiness.gates.durability.status, "blocked");
assert.equal(readiness.durabilityContract.ready, false);
assert.equal(readiness.durabilityContract.blockedLayer, "durability_query");
assert.equal(readiness.durabilityContract.dbLiveEvidenceIsDurabilityEvidence, false);
});
test("configured postgres runtime persists and queries records through query client", async () => {
@@ -132,7 +208,12 @@ test("configured postgres runtime persists and queries records through query cli
assert.equal(health.runtime.migration.ready, true);
assert.equal(health.runtime.gates.migration.status, "ready");
assert.equal(health.runtime.gates.durability.status, "ready");
assert.equal(health.runtime.durabilityContract.ready, true);
assert.equal(health.runtime.durabilityContract.blockedLayer, null);
assert.equal(health.runtime.durabilityContract.dbLiveEvidenceIsDurabilityEvidence, false);
assert.equal(health.readiness.components.runtime, "ready");
assert.equal(health.readiness.durability.status, "ready");
assert.equal(health.readiness.durability.dbLiveEvidenceIsDurabilityEvidence, false);
await rpc("req_01J00000000000000000001001", "gateway.session.register", {
projectId: "prj_01J00000000000000000001000",
@@ -182,7 +263,7 @@ test("configured postgres runtime persists and queries records through query cli
assert.equal(evidence.records[0].operationId, invoke.operationId);
});
function createFakePostgresClient({ migrationReady = true } = {}) {
function createFakePostgresClient({ migrationReady = true, countErrorCode = null } = {}) {
const state = {
gateway_sessions: new Map(),
box_resources: new Map(),
@@ -208,6 +289,11 @@ function createFakePostgresClient({ migrationReady = true } = {}) {
return { rows: schemaRows() };
}
if (sql.startsWith("SELECT COUNT(*)::int AS count FROM ")) {
if (countErrorCode) {
const error = new Error("count query failed");
error.code = countErrorCode;
throw error;
}
const table = sql.match(/FROM ([a-z_]+)/u)?.[1];
return { rows: [{ count: state[table]?.size ?? 0 }] };
}