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:
@@ -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 });
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user