fix: clarify durable Postgres blocker diagnostics

Host commander merge after PR workflow closeout. Reviewed PR #230 against current main; D601 closeout task codex_1779506316145_1 recommended MERGE. Verification recorded: GitHub CLEAN/MERGEABLE; merge-tree clean over current main; focused PR tests node --check plus node --test internal/db/runtime-store.test.mjs internal/cloud/server.test.mjs scripts/src/dev-runtime-migration.test.mjs passed 33/33; synthetic merge including scripts/artifact-runtime-readiness-guard.test.mjs passed 40/40. This merge does not claim live DEV deployment or M3/M4/M5 acceptance; runtime durability remains blocked until deployed and separately verified.
This commit is contained in:
Lyon
2026-05-23 11:26:48 +08:00
committed by GitHub
9 changed files with 577 additions and 25 deletions
+41 -1
View File
@@ -16,7 +16,8 @@ import {
RUNTIME_DURABLE_ADAPTER_DRIVER_MISSING,
RUNTIME_DURABLE_ADAPTER_MIGRATION_BLOCKED,
RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED,
RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED
RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED,
RUNTIME_DURABLE_ADAPTER_SSL_BLOCKED
} from "../../internal/db/runtime-store.mjs";
import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.mjs";
import { ENVIRONMENT_DEV } from "../../internal/protocol/index.mjs";
@@ -572,6 +573,7 @@ function conclusionReadySummary(report) {
}
function blockerScope(blocker) {
if (blocker === RUNTIME_DURABLE_ADAPTER_SSL_BLOCKED) return "runtime-migration-ssl";
if (blocker === RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED) return "runtime-migration-auth";
if (blocker === RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED) return "runtime-migration-schema";
if (blocker === RUNTIME_DURABLE_ADAPTER_MIGRATION_BLOCKED) return "runtime-migration-ledger";
@@ -588,6 +590,13 @@ function classifyMigrationError(error) {
summary: "Postgres driver is unavailable for DEV runtime migration apply."
};
}
if (isMigrationSslError(error)) {
return {
blocker: RUNTIME_DURABLE_ADAPTER_SSL_BLOCKED,
errorCode,
summary: "DEV runtime migration reached Postgres, but SSL negotiation or SSL mode compatibility blocked apply."
};
}
if (["28P01", "28000", "42501"].includes(errorCode)) {
return {
blocker: RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED,
@@ -609,6 +618,37 @@ function classifyMigrationError(error) {
};
}
function isMigrationSslError(error) {
const errorCode = typeof error?.code === "string" ? error.code : "";
if (errorCode.startsWith("ERR_SSL") || errorCode.startsWith("ERR_TLS")) {
return true;
}
if ([
"DEPTH_ZERO_SELF_SIGNED_CERT",
"SELF_SIGNED_CERT_IN_CHAIN",
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
"CERT_HAS_EXPIRED"
].includes(errorCode)) {
return true;
}
const message = typeof error?.message === "string" ? error.message.toLowerCase() : "";
return [
"does not support ssl",
"ssl is not enabled",
"ssl negotiation",
"ssl off",
"ssl required",
"requires ssl",
"no ssl encryption",
"no encryption",
"hostssl",
"server requires ssl",
"before secure tls connection",
"tls handshake"
].some((pattern) => message.includes(pattern));
}
async function writeReport(report, reportPath, root) {
const absolute = path.resolve(root, reportPath);
await mkdir(path.dirname(absolute), { recursive: true });
+67 -1
View File
@@ -55,7 +55,6 @@ test("live dry-run separates migration ledger blocker from auth and schema", asy
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
{
env: {
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:redacted@example.invalid:5432/hwlab",
HWLAB_CLOUD_DB_SSL_MODE: "disable"
},
queryClient: createFakeQueryClient({ migrationReady: false }),
@@ -74,6 +73,73 @@ test("live dry-run separates migration ledger blocker from auth and schema", asy
assert.equal(report.runtime.migration.missing, true);
});
test("live dry-run separates SSL negotiation blocker from auth and schema", async () => {
const report = await buildDevRuntimeMigrationReport(
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
{
env: {
HWLAB_CLOUD_DB_SSL_MODE: "require"
},
queryClient: {
async query(sql) {
assert.match(sql, /information_schema\.columns/u);
const error = new Error("The server does not support SSL connections");
error.code = "08P01";
throw error;
}
},
now: () => "2026-05-22T00:00:00.000Z"
}
);
assert.equal(report.conclusion.status, "blocked");
assert.equal(report.actions.liveDbReadAttempted, true);
assert.equal(report.actions.liveDbWriteAttempted, false);
assert.equal(report.gates.auth.status, "not_checked");
assert.equal(report.gates.schema.status, "not_checked");
assert.equal(report.gates.migration.status, "not_checked");
assert.equal(report.gates.readiness.status, "not_checked");
assert.equal(report.blockers[0].scope, "runtime-migration-ssl");
assert.equal(report.blockers[0].evidence.blocker, "runtime_durable_adapter_ssl_blocked");
assert.equal(report.runtime.blocker, "runtime_durable_adapter_ssl_blocked");
assert.equal(report.runtime.connection.queryResult, "ssl_negotiation_blocked");
assert.equal(JSON.stringify(report).includes("The server does not support SSL connections"), false);
});
test("live dry-run classifies pg_hba no-encryption rejection as SSL with ssl disabled", async () => {
const report = await buildDevRuntimeMigrationReport(
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
{
env: {
HWLAB_CLOUD_DB_SSL_MODE: "disable"
},
queryClient: {
async query(sql) {
assert.match(sql, /information_schema\.columns/u);
const error = new Error('no pg_hba.conf entry for host "[redacted]", user "[redacted]", database "[redacted]", no encryption');
error.code = "28000";
throw error;
}
},
now: () => "2026-05-22T00:00:00.000Z"
}
);
assert.equal(report.conclusion.status, "blocked");
assert.equal(report.actions.liveDbReadAttempted, true);
assert.equal(report.actions.liveDbWriteAttempted, false);
assert.equal(report.gates.auth.status, "not_checked");
assert.equal(report.gates.schema.status, "not_checked");
assert.equal(report.gates.migration.status, "not_checked");
assert.equal(report.gates.readiness.status, "not_checked");
assert.equal(report.blockers[0].scope, "runtime-migration-ssl");
assert.equal(report.blockers[0].evidence.blocker, "runtime_durable_adapter_ssl_blocked");
assert.equal(report.runtime.blocker, "runtime_durable_adapter_ssl_blocked");
assert.equal(report.runtime.connection.queryResult, "ssl_negotiation_blocked");
assert.equal(report.runtime.connection.errorCode, "28000");
assert.equal(JSON.stringify(report).includes("pg_hba.conf"), false);
});
test("apply mode records ledger and verifies durable runtime readiness", async () => {
const queryClient = createFakeQueryClient({ migrationReady: false });
const report = await buildDevRuntimeMigrationReport(