Merge pull request #134 from pikasTech/fix/db-live-readiness-contract

fix: tighten DB live readiness contract
This commit is contained in:
Lyon
2026-05-22 20:27:53 +08:00
committed by GitHub
7 changed files with 112 additions and 9 deletions
+10 -4
View File
@@ -17,8 +17,9 @@ and verifies:
- `GET /health` reports `status: "degraded"` when live DB persistence is not
connected.
- The DB contract reports required env names, redacted Secret refs, env
injection, connection attempt status, connection result, and `liveConnected`
without exposing DB URL or secret values.
injection, connection attempt status, connection result, `liveConnected`,
`liveDbEvidence`, and redaction/safety fields without exposing DB URL or
secret values.
- `gateway.session.register`, `box.resource.register`,
`box.capability.report`, `hardware.invoke.shell`, `audit.event.query`, and
`evidence.record.query` round-trip through the same runtime store.
@@ -45,10 +46,15 @@ keeps all endpoint details redacted and reports only:
- `db.connectionResult`: redacted classifier such as `connected`, `refused`,
`timeout`, `dns_error`, `invalid_url`, or `unsupported_protocol`.
- `db.liveConnected`: the TCP connection completed.
- `db.liveDbEvidence`: true only when the live connection completed; env
presence, fixtures, and disabled probes must keep this false.
- `db.redaction` and `db.safety`: confirm values/endpoints are redacted and
secret material was not read by the health path.
- `db.ready`: currently aliases `db.liveConnected`.
Env presence alone is not treated as green. If the runtime has env injection
but `db.connectionAttempted` is false or `db.liveConnected` is false, the gate
must stay blocked/degraded with the reported blocker.
but `db.connectionAttempted` is false, `db.liveConnected` is false, or
`db.liveDbEvidence` is false, the gate must stay blocked/degraded with the
reported blocker.
Fixture: `fixtures/cloud-api-runtime/minimal-runtime.json`.
+32 -3
View File
@@ -53,6 +53,7 @@ export function buildDbHealthContract(env = process.env) {
environment: DEV_DB_ENV_CONTRACT.environment,
connected,
liveConnected: connected,
liveDbEvidence: connected,
connectionChecked,
connectionAttempted: connection.attempted,
connectionResult: connection.result,
@@ -73,12 +74,16 @@ export function buildDbHealthContract(env = process.env) {
redacted: true
})),
connection,
redaction: buildRedactionSafety(connection),
safety: {
devOnly: true,
prodAllowed: DEV_DB_ENV_CONTRACT.forbidden.prodAllowed,
secretsRead: false,
secretMaterialRead: false,
valuesRedacted: true,
liveDbEvidence: false
endpointRedacted: true,
liveDbEvidence: connected,
liveDbConnectedEvidence: connected
},
blocker: configReady ? "live DB connection has not been attempted by this health contract" : missingEnvBlocker(missingEnv),
evidence: configReady ? "env_presence_only_no_live_db" : "env_contract_blocked"
@@ -125,6 +130,7 @@ export function summarizeDbContract(db = buildDbHealthContract()) {
connectionChecked: db.connectionChecked,
connectionAttempted: db.connectionAttempted ?? db.connectionChecked,
connectionResult: db.connectionResult ?? db.connection?.result ?? "unknown",
liveDbEvidence: Boolean(db.liveDbEvidence ?? db.safety?.liveDbEvidence),
requiredEnv: db.fields.map((field) => ({
name: field.name,
present: field.present,
@@ -154,8 +160,8 @@ export function summarizeDbContract(db = buildDbHealthContract()) {
})
),
connection: summarizeConnection(db.connection),
redaction: summarizeRedaction(db.redaction),
blocker: db.blocker ?? null,
liveDbEvidence: db.safety.liveDbEvidence,
fixtureEvidence: false
};
}
@@ -184,6 +190,16 @@ function buildNotAttemptedConnection(configReady, missingEnv) {
};
}
function buildRedactionSafety(connection) {
return {
valuesRedacted: true,
endpointRedacted: connection?.endpointRedacted !== false,
valueRedacted: connection?.valueRedacted !== false,
secretMaterialRead: false,
secretRefsOnly: true
};
}
async function probeDbConnection(env, options) {
const timeoutMs = normalizeTimeoutMs(options.timeoutMs ?? env?.HWLAB_CLOUD_DB_PROBE_TIMEOUT_MS);
const parsed = parseDbTarget(env?.HWLAB_CLOUD_DB_URL);
@@ -339,6 +355,7 @@ function applyConnectionResult(base, connection) {
...base,
connected: liveConnected,
liveConnected,
liveDbEvidence: liveConnected,
connectionChecked: connection.attempted,
connectionAttempted: connection.attempted,
connectionResult: connection.result,
@@ -346,9 +363,11 @@ function applyConnectionResult(base, connection) {
status: liveConnected ? "ready" : "degraded",
mode: liveConnected ? "live_connection_ready" : "live_connection_blocked",
connection,
redaction: buildRedactionSafety(connection),
safety: {
...base.safety,
liveDbEvidence: connection.attempted,
endpointRedacted: connection.endpointRedacted !== false,
liveDbEvidence: liveConnected,
liveDbConnectedEvidence: liveConnected
},
blocker: liveConnected ? null : blockerForConnection(connection),
@@ -412,3 +431,13 @@ function summarizeConnection(connection) {
errorCode: connection.errorCode ?? null
};
}
function summarizeRedaction(redaction) {
return {
valuesRedacted: redaction?.valuesRedacted !== false,
endpointRedacted: redaction?.endpointRedacted !== false,
valueRedacted: redaction?.valueRedacted !== false,
secretMaterialRead: redaction?.secretMaterialRead === true,
secretRefsOnly: redaction?.secretRefsOnly !== false
};
}
+7
View File
@@ -60,6 +60,13 @@ test("system.health includes the redacted DEV DB env contract", async () => {
assert.equal(response.result.status, "degraded");
assert.equal(response.result.db.status, "blocked");
assert.equal(response.result.db.connected, false);
assert.equal(response.result.db.liveConnected, false);
assert.equal(response.result.db.liveDbEvidence, false);
assert.equal(response.result.db.connectionAttempted, false);
assert.equal(response.result.db.connectionResult, "not_attempted_missing_env");
assert.equal(response.result.db.redaction.valuesRedacted, true);
assert.equal(response.result.db.redaction.secretMaterialRead, false);
assert.equal(response.result.db.safety.liveDbEvidence, false);
assert.equal(response.result.runtime.durable, false);
assert.ok(response.result.db.missingEnv.includes("HWLAB_CLOUD_DB_URL"));
assert.equal(response.result.db.secretRefs[0].secretName, "hwlab-cloud-api-dev-db");
+59
View File
@@ -28,8 +28,12 @@ test("cloud api exposes /health, /health/live, and /live probes", async () => {
assert.equal(healthPayload.db.status, "blocked");
assert.equal(healthPayload.db.connected, false);
assert.equal(healthPayload.db.liveConnected, false);
assert.equal(healthPayload.db.liveDbEvidence, false);
assert.equal(healthPayload.db.connectionAttempted, false);
assert.equal(healthPayload.db.connectionResult, "not_attempted_missing_env");
assert.equal(healthPayload.db.redaction.valuesRedacted, true);
assert.equal(healthPayload.db.redaction.secretMaterialRead, false);
assert.equal(healthPayload.db.safety.liveDbEvidence, false);
assert.equal(healthPayload.runtime.durable, false);
assert.equal(healthPayload.runtime.status, "degraded");
assert.deepEqual(healthPayload.db.missingEnv, ["HWLAB_CLOUD_DB_URL", "HWLAB_CLOUD_DB_SSL_MODE"]);
@@ -88,12 +92,18 @@ test("cloud api health reports DB env presence and live connection classificatio
assert.equal(payload.db.status, "ready");
assert.equal(payload.db.connected, true);
assert.equal(payload.db.liveConnected, true);
assert.equal(payload.db.liveDbEvidence, true);
assert.equal(payload.db.connectionChecked, true);
assert.equal(payload.db.connectionAttempted, true);
assert.equal(payload.db.connectionResult, "connected");
assert.equal(payload.db.configReady, true);
assert.equal(payload.db.ready, true);
assert.equal(payload.db.evidence, "live_db_tcp_connection_ready");
assert.equal(payload.db.safety.liveDbEvidence, true);
assert.equal(payload.db.safety.liveDbConnectedEvidence, true);
assert.equal(payload.db.redaction.valuesRedacted, true);
assert.equal(payload.db.redaction.endpointRedacted, true);
assert.equal(payload.db.redaction.secretMaterialRead, false);
assert.deepEqual(payload.db.missingEnv, []);
assert.equal(JSON.stringify(payload.db).includes("password"), false);
assert.equal(JSON.stringify(payload.db).includes("127.0.0.1"), false);
@@ -119,3 +129,52 @@ test("cloud api health reports DB env presence and live connection classificatio
});
}
});
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;
const originalProbeDisabled = process.env.HWLAB_CLOUD_DB_PROBE_DISABLED;
process.env.HWLAB_CLOUD_DB_URL = "postgres://user:password@db.example.invalid:5432/hwlab";
process.env.HWLAB_CLOUD_DB_SSL_MODE = "require";
process.env.HWLAB_CLOUD_DB_PROBE_DISABLED = "1";
const server = createCloudApiServer();
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.db.configReady, true);
assert.equal(payload.db.connected, false);
assert.equal(payload.db.liveConnected, false);
assert.equal(payload.db.liveDbEvidence, false);
assert.equal(payload.db.safety.liveDbEvidence, false);
assert.equal(payload.db.ready, false);
assert.equal(payload.db.connectionAttempted, false);
assert.equal(payload.db.connectionResult, "not_attempted");
assert.equal(payload.db.evidence, "env_presence_only_probe_disabled");
assert.equal(JSON.stringify(payload.db).includes("password"), false);
assert.equal(JSON.stringify(payload.db).includes("db.example.invalid"), 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;
}
if (originalProbeDisabled === undefined) {
delete process.env.HWLAB_CLOUD_DB_PROBE_DISABLED;
} else {
process.env.HWLAB_CLOUD_DB_PROBE_DISABLED = originalProbeDisabled;
}
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
}
});
+1
View File
@@ -87,6 +87,7 @@ async function run() {
assert.equal(health.body.db.status, "blocked");
assert.equal(health.body.db.connected, false);
assert.equal(health.body.db.liveConnected, false);
assert.equal(health.body.db.liveDbEvidence, false);
assert.equal(health.body.db.connectionAttempted, false);
assert.equal(health.body.db.connectionResult, "not_attempted_missing_env");
assert.equal(health.body.db.ready, false);
+1
View File
@@ -154,6 +154,7 @@ async function smokeCloudApi() {
assert.equal(health.body.status, "degraded");
assert.equal(health.body.db.connected, false);
assert.equal(health.body.db.liveConnected, false);
assert.equal(health.body.db.liveDbEvidence, false);
assert.equal(health.body.db.connectionAttempted, false);
assert.equal(health.body.db.connectionResult, "not_attempted_missing_env");
assert.equal(health.body.db.status, "blocked");
+2 -2
View File
@@ -394,9 +394,9 @@ function summarizeRuntimeDbReadiness(db, secretPresence = unknownDbSecretPresenc
connectionAttempted,
connectionResult,
liveConnected,
valuesRedacted: db.safety?.valuesRedacted === true,
valuesRedacted: db.redaction?.valuesRedacted !== false && db.safety?.valuesRedacted === true,
secretMaterialRead: false,
liveDbEvidence: Boolean(db.safety?.liveDbEvidence),
liveDbEvidence: Boolean(db.liveDbEvidence ?? db.safety?.liveDbEvidence),
blocker: blockerMessage,
evidence: [
{