fix: disable dev cloud api db ssl mode
This commit is contained in:
+1
-1
@@ -18,7 +18,7 @@ for future compatibility, but PROD deployment is not part of MVP acceptance.
|
||||
- `hwlab-cloud-api` declares the DEV DB env contract with
|
||||
`HWLAB_CLOUD_DB_URL` from Secret reference
|
||||
`hwlab-cloud-api-dev-db/database-url` and non-secret
|
||||
`HWLAB_CLOUD_DB_SSL_MODE=require`. Runtime DB readiness dials the redacted
|
||||
`HWLAB_CLOUD_DB_SSL_MODE=disable`. Runtime DB readiness dials the redacted
|
||||
host parsed from that Secret URL. `cloud-api-db` is only an optional desired
|
||||
alias until Service plus Endpoint/EndpointSlice ownership and rollout/apply
|
||||
contract exist; see
|
||||
|
||||
+1
-1
@@ -194,7 +194,7 @@
|
||||
"HWLAB_IMAGE_TAG": "7de6edd",
|
||||
"HWLAB_RUNTIME_SUBSTITUTE_FORBIDDEN": "unidesk-backend,provider-gateway,microservice-proxy",
|
||||
"HWLAB_CLOUD_DB_URL": "secretRef:hwlab-cloud-api-dev-db/database-url",
|
||||
"HWLAB_CLOUD_DB_SSL_MODE": "require",
|
||||
"HWLAB_CLOUD_DB_SSL_MODE": "disable",
|
||||
"HWLAB_CLOUD_DB_CONTRACT": "dev-redacted-presence-only",
|
||||
"HWLAB_CLOUD_RUNTIME_ADAPTER": "postgres",
|
||||
"HWLAB_CLOUD_RUNTIME_DURABLE": "true",
|
||||
|
||||
@@ -365,7 +365,7 @@
|
||||
},
|
||||
"HWLAB_CLOUD_DB_SSL_MODE": {
|
||||
"type": "string",
|
||||
"const": "require"
|
||||
"const": "disable"
|
||||
},
|
||||
"HWLAB_CLOUD_DB_CONTRACT": {
|
||||
"type": "string",
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
},
|
||||
{
|
||||
"name": "HWLAB_CLOUD_DB_SSL_MODE",
|
||||
"value": "require"
|
||||
"value": "disable"
|
||||
},
|
||||
{
|
||||
"name": "HWLAB_CLOUD_DB_CONTRACT",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"data": {
|
||||
"endpoint": "http://74.48.78.17:16667",
|
||||
"cloud-api": "GET /health/live via hwlab-edge-proxy then hwlab-cloud-api:6667",
|
||||
"cloud-api-db": "DEV DB config gate requires HWLAB_CLOUD_DB_URL from Secret hwlab-cloud-api-dev-db/database-url and HWLAB_CLOUD_DB_SSL_MODE=require. Runtime readiness dials the redacted host parsed from the Secret URL and reports endpointSource=secret-url-host. cloud-api-db is an optional desired alias only until this repo owns Service plus Endpoint or EndpointSlice manifests and rollout/apply contract; health reports env presence, env injection, redacted connection attempt/result, and liveConnected without exposing secret values",
|
||||
"cloud-api-db": "DEV DB config gate requires HWLAB_CLOUD_DB_URL from Secret hwlab-cloud-api-dev-db/database-url and HWLAB_CLOUD_DB_SSL_MODE=disable. Runtime readiness dials the redacted host parsed from the Secret URL and reports endpointSource=secret-url-host. cloud-api-db is an optional desired alias only until this repo owns Service plus Endpoint or EndpointSlice manifests and rollout/apply contract; health reports env presence, env injection, redacted connection attempt/result, and liveConnected without exposing secret values",
|
||||
"cloud-web": "GET /health/live on hwlab-cloud-web:8080; consumes cloud-api only",
|
||||
"agent": "hwlab-agent-mgr readiness gates worker template creation; hwlab-agent-worker is suspended until a session is scheduled",
|
||||
"sim": "hwlab-gateway-simu:7101 and hwlab-box-simu:7201 expose /health/live for DEV smoke",
|
||||
|
||||
@@ -289,7 +289,7 @@ unchanged.
|
||||
`hwlab-cloud-api` has a DEV DB env contract placeholder:
|
||||
`HWLAB_CLOUD_DB_URL` must come from Secret reference
|
||||
`hwlab-cloud-api-dev-db/database-url`, and
|
||||
`HWLAB_CLOUD_DB_SSL_MODE=require`. Runtime readiness dials the redacted host
|
||||
`HWLAB_CLOUD_DB_SSL_MODE=disable`. Runtime readiness dials the redacted host
|
||||
parsed from that Secret URL. `cloud-api-db` remains an optional desired alias,
|
||||
not a readiness gate or TCP probe target, until this repo owns its
|
||||
Service/Endpoint rollout contract. The local health gate reports DB runtime
|
||||
|
||||
@@ -35,7 +35,7 @@ Required DEV DB config names:
|
||||
|
||||
- `HWLAB_CLOUD_DB_URL` from Secret reference
|
||||
`hwlab-cloud-api-dev-db/database-url`.
|
||||
- `HWLAB_CLOUD_DB_SSL_MODE=require`.
|
||||
- `HWLAB_CLOUD_DB_SSL_MODE=disable`.
|
||||
- Runtime TCP dial target: the redacted host parsed from
|
||||
`HWLAB_CLOUD_DB_URL`, reported as `endpointSource="secret-url-host"`.
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ The preflight checks:
|
||||
- `hwlab-cloud-api` declares the DEV DB env contract:
|
||||
`HWLAB_CLOUD_DB_URL` from Secret reference
|
||||
`hwlab-cloud-api-dev-db/database-url` and
|
||||
`HWLAB_CLOUD_DB_SSL_MODE=require`. Runtime readiness dials the redacted host
|
||||
`HWLAB_CLOUD_DB_SSL_MODE=disable`. Runtime readiness dials the redacted host
|
||||
parsed from that Secret URL. `cloud-api-db` is an optional desired alias only
|
||||
until this repo owns Service plus Endpoint or EndpointSlice manifests and
|
||||
rollout/apply contract.
|
||||
|
||||
@@ -40,7 +40,7 @@ export const DEV_DB_ENV_CONTRACT = Object.freeze({
|
||||
"schema"
|
||||
]),
|
||||
nonSecretDefaults: Object.freeze({
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "require"
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
||||
}),
|
||||
forbidden: Object.freeze({
|
||||
prodAllowed: false,
|
||||
|
||||
@@ -122,7 +122,7 @@ test("cloud api health reports DB env presence and live connection classificatio
|
||||
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@127.0.0.1:${dbPort}/hwlab`;
|
||||
process.env.HWLAB_CLOUD_DB_SSL_MODE = "require";
|
||||
process.env.HWLAB_CLOUD_DB_SSL_MODE = "disable";
|
||||
delete process.env.HWLAB_CLOUD_DB_SERVICE_NAME;
|
||||
delete process.env.HWLAB_CLOUD_DB_SERVICE_NAMESPACE;
|
||||
delete process.env.HWLAB_CLOUD_DB_HOST;
|
||||
@@ -213,7 +213,7 @@ test("cloud api health separates DB connected from durable runtime schema readin
|
||||
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@127.0.0.1:${dbPort}/hwlab`;
|
||||
process.env.HWLAB_CLOUD_DB_SSL_MODE = "require";
|
||||
process.env.HWLAB_CLOUD_DB_SSL_MODE = "disable";
|
||||
|
||||
const server = createCloudApiServer({
|
||||
runtimeStore: {
|
||||
@@ -302,7 +302,7 @@ test("cloud api health keeps DB live evidence separate when durable adapter quer
|
||||
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";
|
||||
process.env.HWLAB_CLOUD_DB_SSL_MODE = "disable";
|
||||
|
||||
const server = createCloudApiServer({
|
||||
runtimeStore: {
|
||||
@@ -404,7 +404,7 @@ test("cloud api health does not treat DB env presence-only as live readiness", a
|
||||
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_SSL_MODE = "disable";
|
||||
delete process.env.HWLAB_CLOUD_DB_PROBE_DISABLED;
|
||||
|
||||
const server = createCloudApiServer();
|
||||
|
||||
@@ -18,13 +18,17 @@ async function makeFixture({
|
||||
deployEnvMirrors = true,
|
||||
deploySkillsCommitId = null,
|
||||
deployCodeAgentProviderEnv = true,
|
||||
deployDbEnv = true,
|
||||
deployDbSslMode = "disable",
|
||||
workloadTag = commitId,
|
||||
workloadEnvCommitId = commitId,
|
||||
workloadEnvImageTag = commitId,
|
||||
workloadEnvImage = null,
|
||||
workloadEnvMirrors = true,
|
||||
workloadSkillsCommitId = null,
|
||||
workloadCodeAgentProviderEnv = true
|
||||
workloadCodeAgentProviderEnv = true,
|
||||
workloadDbEnv = true,
|
||||
workloadDbSslMode = "disable"
|
||||
} = {}) {
|
||||
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-desired-state-"));
|
||||
await mkdir(path.join(root, "deploy/k8s/base"), { recursive: true });
|
||||
@@ -59,6 +63,28 @@ async function makeFixture({
|
||||
}
|
||||
]
|
||||
: [];
|
||||
const dbEnv =
|
||||
serviceId === "hwlab-cloud-api" && deployDbEnv
|
||||
? {
|
||||
HWLAB_CLOUD_DB_URL: "secretRef:hwlab-cloud-api-dev-db/database-url",
|
||||
HWLAB_CLOUD_DB_SSL_MODE: deployDbSslMode
|
||||
}
|
||||
: {};
|
||||
const workloadDbEntries =
|
||||
serviceId === "hwlab-cloud-api" && workloadDbEnv
|
||||
? [
|
||||
{
|
||||
name: "HWLAB_CLOUD_DB_URL",
|
||||
valueFrom: {
|
||||
secretKeyRef: {
|
||||
name: "hwlab-cloud-api-dev-db",
|
||||
key: "database-url"
|
||||
}
|
||||
}
|
||||
},
|
||||
{ name: "HWLAB_CLOUD_DB_SSL_MODE", value: workloadDbSslMode }
|
||||
]
|
||||
: [];
|
||||
const deploy = {
|
||||
manifestVersion: "v1",
|
||||
environment: "dev",
|
||||
@@ -77,6 +103,7 @@ async function makeFixture({
|
||||
HWLAB_IMAGE: deployEnvImage ?? image,
|
||||
HWLAB_IMAGE_TAG: deployEnvImageTag,
|
||||
...(deploySkillsCommitId ? { HWLAB_SKILLS_COMMIT_ID: deploySkillsCommitId } : {}),
|
||||
...dbEnv,
|
||||
...providerEnv
|
||||
}
|
||||
: {}
|
||||
@@ -136,6 +163,7 @@ async function makeFixture({
|
||||
{ name: "HWLAB_IMAGE", value: workloadEnvImage ?? image },
|
||||
{ name: "HWLAB_IMAGE_TAG", value: workloadEnvImageTag },
|
||||
...(workloadSkillsCommitId ? [{ name: "HWLAB_SKILLS_COMMIT_ID", value: workloadSkillsCommitId }] : []),
|
||||
...workloadDbEntries,
|
||||
...workloadProviderEnv
|
||||
]
|
||||
: []
|
||||
@@ -174,12 +202,30 @@ test("passes when cloud-api preserves provider env and DEV egress contract", asy
|
||||
const repoRoot = await makeFixture();
|
||||
const plan = await buildDesiredStatePlan({ repoRoot });
|
||||
assert.equal(plan.status, "pass");
|
||||
assert.equal(plan.cloudApiDb.ready, true);
|
||||
assert.equal(plan.cloudApiDb.sslMode.expected, "disable");
|
||||
assert.equal(plan.cloudApiDb.sslMode.deployMatches, true);
|
||||
assert.equal(plan.cloudApiDb.sslMode.workloadMatches, true);
|
||||
assert.equal(plan.codeAgentProvider.ready, true);
|
||||
assert.equal(plan.codeAgentProvider.secretRef.present, true);
|
||||
assert.equal(plan.codeAgentProvider.egress.deployMatchesDevProxy, true);
|
||||
assert.deepEqual(plan.diagnostics, []);
|
||||
});
|
||||
|
||||
test("blocks when cloud-api DB SSL mode drifts back to require", async () => {
|
||||
const repoRoot = await makeFixture({
|
||||
deployDbSslMode: "require",
|
||||
workloadDbSslMode: "require"
|
||||
});
|
||||
const plan = await buildDesiredStatePlan({ repoRoot });
|
||||
assert.equal(plan.status, "blocked");
|
||||
assert.equal(plan.cloudApiDb.ready, false);
|
||||
assert.equal(plan.cloudApiDb.sslMode.expected, "disable");
|
||||
assert.equal(plan.cloudApiDb.sslMode.deployMatches, false);
|
||||
assert.equal(plan.cloudApiDb.sslMode.workloadMatches, false);
|
||||
assert.ok(plan.diagnostics.some((diagnostic) => diagnostic.code === "cloud_api_db_contract_mismatch"));
|
||||
});
|
||||
|
||||
test("blocks when cloud-web runtime identity mirrors are missing", async () => {
|
||||
const repoRoot = await makeFixture({
|
||||
serviceId: "hwlab-cloud-web",
|
||||
|
||||
@@ -192,7 +192,13 @@ function validateSource(ctx, manifest) {
|
||||
function validateCloudApiDbSource(ctx, env) {
|
||||
const alias = DEV_DB_ENV_CONTRACT.dns;
|
||||
expectEqual(ctx, env.HWLAB_CLOUD_DB_URL, "secretRef:hwlab-cloud-api-dev-db/database-url", "$.services.hwlab-cloud-api.env.HWLAB_CLOUD_DB_URL", "cloud API DB URL Secret reference");
|
||||
expectEqual(ctx, env.HWLAB_CLOUD_DB_SSL_MODE, "require", "$.services.hwlab-cloud-api.env.HWLAB_CLOUD_DB_SSL_MODE", "cloud API DB SSL mode");
|
||||
expectEqual(
|
||||
ctx,
|
||||
env.HWLAB_CLOUD_DB_SSL_MODE,
|
||||
DEV_DB_ENV_CONTRACT.nonSecretDefaults.HWLAB_CLOUD_DB_SSL_MODE,
|
||||
"$.services.hwlab-cloud-api.env.HWLAB_CLOUD_DB_SSL_MODE",
|
||||
"cloud API DEV DB SSL mode"
|
||||
);
|
||||
expectEqual(ctx, env.HWLAB_CLOUD_DB_CONTRACT, "dev-redacted-presence-only", "$.services.hwlab-cloud-api.env.HWLAB_CLOUD_DB_CONTRACT", "cloud API DB redacted contract marker");
|
||||
expectEqual(ctx, DEV_DB_ENV_CONTRACT.endpointAuthority.source, "secret-url-host", "internal/cloud/db-contract.mjs.endpointAuthority.source", "cloud API DB runtime authority source");
|
||||
expectEqual(ctx, DEV_DB_ENV_CONTRACT.endpointAuthority.env, "HWLAB_CLOUD_DB_URL", "internal/cloud/db-contract.mjs.endpointAuthority.env", "cloud API DB runtime authority env");
|
||||
@@ -343,6 +349,13 @@ function validateCloudApiDbOptionalAliasArtifacts(ctx, services, workloads) {
|
||||
expectEqual(ctx, service, undefined, "deploy/k8s/base/services.yaml.cloud-api-db", "cloud-api-db remains optional until Service/Endpoint ownership and rollout contract exist");
|
||||
const container = mapByServiceId(listItems(workloads)).get("hwlab-cloud-api")?.spec?.template?.spec?.containers?.[0];
|
||||
const env = new Map((container?.env ?? []).map((entry) => [entry.name, entry.value]));
|
||||
expectEqual(
|
||||
ctx,
|
||||
env.get("HWLAB_CLOUD_DB_SSL_MODE"),
|
||||
DEV_DB_ENV_CONTRACT.nonSecretDefaults.HWLAB_CLOUD_DB_SSL_MODE,
|
||||
"deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.HWLAB_CLOUD_DB_SSL_MODE",
|
||||
"cloud API workload DEV DB SSL mode"
|
||||
);
|
||||
for (const name of [
|
||||
"HWLAB_CLOUD_DB_SERVICE_NAME",
|
||||
"HWLAB_CLOUD_DB_SERVICE_NAMESPACE",
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
DEV_CODE_AGENT_PROVIDER_CONTRACT,
|
||||
inspectCodeAgentProviderManifestRefs
|
||||
} from "../../internal/cloud/code-agent-contract.mjs";
|
||||
import { DEV_DB_ENV_CONTRACT } from "../../internal/cloud/db-contract.mjs";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
@@ -329,6 +330,100 @@ function inspectCodeAgentProviderDesiredState(ctx, deployService, workloadRecord
|
||||
return inspection;
|
||||
}
|
||||
|
||||
function dbSecretRefPlaceholder() {
|
||||
const ref = DEV_DB_ENV_CONTRACT.secretRefs[0];
|
||||
return `secretRef:${ref.secretName}/${ref.secretKey}`;
|
||||
}
|
||||
|
||||
function secretRefMatches(entry, ref) {
|
||||
return (
|
||||
entry?.valueFrom?.secretKeyRef?.name === ref.secretName &&
|
||||
entry?.valueFrom?.secretKeyRef?.key === ref.secretKey
|
||||
);
|
||||
}
|
||||
|
||||
function inspectCloudApiDbDesiredState(ctx, deployService, workloadRecords) {
|
||||
const workloadRecord = workloadRecords.find((workload) => workload.containerName === "hwlab-cloud-api") ?? workloadRecords[0] ?? null;
|
||||
const deployEnv = deployService?.env ?? {};
|
||||
const workloadEnv = workloadRecord?.env ?? {};
|
||||
const secretRef = DEV_DB_ENV_CONTRACT.secretRefs[0];
|
||||
const expectedSecretRef = dbSecretRefPlaceholder();
|
||||
const expectedSslMode = DEV_DB_ENV_CONTRACT.nonSecretDefaults.HWLAB_CLOUD_DB_SSL_MODE;
|
||||
const deploySecretMatches = deployEnv.HWLAB_CLOUD_DB_URL === expectedSecretRef;
|
||||
const workloadSecretMatches = secretRefMatches(workloadEnv.HWLAB_CLOUD_DB_URL, secretRef);
|
||||
const deploySslMatches = deployEnv.HWLAB_CLOUD_DB_SSL_MODE === expectedSslMode;
|
||||
const workloadSslMatches = workloadEnv.HWLAB_CLOUD_DB_SSL_MODE === expectedSslMode;
|
||||
const ready = deploySecretMatches && workloadSecretMatches && deploySslMatches && workloadSslMatches;
|
||||
|
||||
if (!ready) {
|
||||
addDiagnostic(ctx, {
|
||||
code: "cloud_api_db_contract_mismatch",
|
||||
path: "deploy.cloudApiDb",
|
||||
message: "hwlab-cloud-api desired state must preserve DEV DB Secret ref and non-SSL runtime mode",
|
||||
expected: {
|
||||
secretRef: `${secretRef.secretName}/${secretRef.secretKey}`,
|
||||
sslMode: expectedSslMode
|
||||
},
|
||||
actual: {
|
||||
deploySecretRef: deploySecretMatches ? "secretRef:declared" : "missing_or_mismatched",
|
||||
workloadSecretRef: workloadSecretMatches ? "secretRef:declared" : "missing_or_mismatched",
|
||||
deploySslMode: deployEnv.HWLAB_CLOUD_DB_SSL_MODE ?? null,
|
||||
workloadSslMode: workloadEnv.HWLAB_CLOUD_DB_SSL_MODE ?? null
|
||||
},
|
||||
secretMaterialRead: false,
|
||||
valuesRedacted: true
|
||||
});
|
||||
}
|
||||
|
||||
recordObservation(ctx, {
|
||||
source: "deploy",
|
||||
serviceId: "hwlab-cloud-api",
|
||||
path: `${deployPath}.services.hwlab-cloud-api.env.HWLAB_CLOUD_DB_SSL_MODE`,
|
||||
field: "HWLAB_CLOUD_DB_SSL_MODE",
|
||||
kind: "runtime-config",
|
||||
value: deployEnv.HWLAB_CLOUD_DB_SSL_MODE ?? null,
|
||||
expected: expectedSslMode
|
||||
});
|
||||
recordObservation(ctx, {
|
||||
source: "workload",
|
||||
serviceId: "hwlab-cloud-api",
|
||||
path: `${workloadsPath}.hwlab-cloud-api.env.HWLAB_CLOUD_DB_SSL_MODE`,
|
||||
field: "HWLAB_CLOUD_DB_SSL_MODE",
|
||||
kind: "runtime-config",
|
||||
value: workloadEnv.HWLAB_CLOUD_DB_SSL_MODE ?? null,
|
||||
expected: expectedSslMode
|
||||
});
|
||||
|
||||
return {
|
||||
contractVersion: DEV_DB_ENV_CONTRACT.contractVersion,
|
||||
environment: DEV_DB_ENV_CONTRACT.environment,
|
||||
status: ready ? "pass" : "blocked",
|
||||
ready,
|
||||
secretRef: {
|
||||
env: secretRef.env,
|
||||
secretName: secretRef.secretName,
|
||||
secretKey: secretRef.secretKey,
|
||||
deployPlaceholderPresent: deploySecretMatches,
|
||||
workloadSecretRefPresent: workloadSecretMatches,
|
||||
present: deploySecretMatches && workloadSecretMatches,
|
||||
redacted: true
|
||||
},
|
||||
sslMode: {
|
||||
env: "HWLAB_CLOUD_DB_SSL_MODE",
|
||||
value: deployEnv.HWLAB_CLOUD_DB_SSL_MODE ?? null,
|
||||
workloadValue: workloadEnv.HWLAB_CLOUD_DB_SSL_MODE ?? null,
|
||||
expected: expectedSslMode,
|
||||
deployMatches: deploySslMatches,
|
||||
workloadMatches: workloadSslMatches,
|
||||
secret: false
|
||||
},
|
||||
secretMaterialRead: false,
|
||||
valuesRedacted: true,
|
||||
liveDbEvidence: false,
|
||||
fixtureEvidence: false
|
||||
};
|
||||
}
|
||||
|
||||
function indexWorkloads(workloads) {
|
||||
const byService = new Map();
|
||||
for (const item of listItems(workloads)) {
|
||||
@@ -616,6 +711,7 @@ export async function buildDesiredStatePlan(options = {}) {
|
||||
|
||||
const services = [];
|
||||
let codeAgentProvider = null;
|
||||
let cloudApiDb = null;
|
||||
for (const serviceId of serviceIds) {
|
||||
const deployService = deployByService.get(serviceId);
|
||||
const catalogService = catalogByService.get(serviceId);
|
||||
@@ -774,6 +870,7 @@ export async function buildDesiredStatePlan(options = {}) {
|
||||
services.push(service);
|
||||
|
||||
if (serviceId === "hwlab-cloud-api") {
|
||||
cloudApiDb = inspectCloudApiDbDesiredState(ctx, deployService, workloadsForService);
|
||||
codeAgentProvider = inspectCodeAgentProviderDesiredState(ctx, deployService, workloadsForService);
|
||||
}
|
||||
}
|
||||
@@ -838,6 +935,13 @@ export async function buildDesiredStatePlan(options = {}) {
|
||||
nonAuthoritativeEvidence: [artifactReportPath],
|
||||
note: "This planner reviews source desired-state only. It does not prove image existence, registry reachability, a real DEV apply, or M3 DEV-LIVE hardware-loop evidence."
|
||||
},
|
||||
cloudApiDb: cloudApiDb ?? {
|
||||
status: "blocked",
|
||||
ready: false,
|
||||
secretMaterialRead: false,
|
||||
valuesRedacted: true,
|
||||
liveDbEvidence: false
|
||||
},
|
||||
codeAgentProvider: codeAgentProvider ?? {
|
||||
status: "blocked",
|
||||
ready: false,
|
||||
|
||||
@@ -1133,14 +1133,33 @@ async function checkCloudApiDb(deploy, workloads, services, blockers) {
|
||||
const missingSecretRefs = requiredEnv
|
||||
.filter((env) => env.secretRef && !env.secretRef.present)
|
||||
.map((env) => `${env.secretRef.secretName}/${env.secretRef.secretKey}`);
|
||||
const expectedSslMode = DEV_DB_ENV_CONTRACT.nonSecretDefaults.HWLAB_CLOUD_DB_SSL_MODE;
|
||||
const sslModeMatches =
|
||||
deployEnv.HWLAB_CLOUD_DB_SSL_MODE === expectedSslMode &&
|
||||
workloadEnv.get("HWLAB_CLOUD_DB_SSL_MODE")?.value === expectedSslMode;
|
||||
const mismatchedNonSecretEnv = sslModeMatches
|
||||
? []
|
||||
: [`HWLAB_CLOUD_DB_SSL_MODE must be ${expectedSslMode}`];
|
||||
const dnsContract = inspectCloudApiDbOptionalAliasContract(deployEnv, workloadEnv, services);
|
||||
const missingDnsContract = dnsContract.missing;
|
||||
if (missingManifest.length > 0 || missingK8s.length > 0 || missingSecretRefs.length > 0 || missingDnsContract.length > 0) {
|
||||
if (
|
||||
missingManifest.length > 0 ||
|
||||
missingK8s.length > 0 ||
|
||||
missingSecretRefs.length > 0 ||
|
||||
mismatchedNonSecretEnv.length > 0 ||
|
||||
missingDnsContract.length > 0
|
||||
) {
|
||||
addBlocker(
|
||||
blockers,
|
||||
"runtime_blocker",
|
||||
"cloud-api-db-config",
|
||||
`cloud-api DEV DB env/authority contract is incomplete; missing ${[...missingManifest, ...missingK8s, ...missingSecretRefs, ...missingDnsContract].join(", ")}`
|
||||
`cloud-api DEV DB env/authority contract is incomplete; missing ${[
|
||||
...missingManifest,
|
||||
...missingK8s,
|
||||
...missingSecretRefs,
|
||||
...mismatchedNonSecretEnv,
|
||||
...missingDnsContract
|
||||
].join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1148,6 +1167,7 @@ async function checkCloudApiDb(deploy, workloads, services, blockers) {
|
||||
missingManifest.length === 0 &&
|
||||
missingK8s.length === 0 &&
|
||||
missingSecretRefs.length === 0 &&
|
||||
mismatchedNonSecretEnv.length === 0 &&
|
||||
missingDnsContract.length === 0;
|
||||
const health = {
|
||||
status: configReady ? "degraded" : "blocked",
|
||||
@@ -1181,6 +1201,7 @@ async function checkCloudApiDb(deploy, workloads, services, blockers) {
|
||||
missingManifest,
|
||||
missingK8s,
|
||||
missingSecretRefs,
|
||||
mismatchedNonSecretEnv,
|
||||
missingDnsContract,
|
||||
dns: dnsContract,
|
||||
runtimeHealth: summarizeDbContract(health),
|
||||
|
||||
@@ -834,20 +834,21 @@ function validateCloudApiDbContract(reporter, deploy, workloads, services) {
|
||||
...staticContract.missingDeployEnv,
|
||||
...staticContract.missingK8sEnv,
|
||||
...staticContract.missingSecretRefs,
|
||||
...staticContract.mismatchedNonSecretEnv,
|
||||
...staticContract.missingDnsContract
|
||||
];
|
||||
reporter.check(
|
||||
"cloud-api-db-env-contract",
|
||||
"db",
|
||||
"blocked",
|
||||
`cloud-api DEV DB manifest is missing ${missing.join(", ")}.`,
|
||||
`cloud-api DEV DB manifest contract is blocked by ${missing.join(", ")}.`,
|
||||
evidence
|
||||
);
|
||||
reporter.block({
|
||||
type: "contract_blocker",
|
||||
scope: "cloud-api-db-env-contract",
|
||||
summary: `cloud-api DEV DB contract is incomplete; missing ${missing.join(", ")}.`,
|
||||
nextTask: "Repair deploy/deploy.json and deploy/k8s/base/workloads.yaml so they expose the required DB env names and redacted Secret reference. Do not make cloud-api-db a hard readiness gate unless this repo also owns its Service/Endpoint rollout contract."
|
||||
summary: `cloud-api DEV DB contract is incomplete; blocked by ${missing.join(", ")}.`,
|
||||
nextTask: "Repair deploy/deploy.json and deploy/k8s/base/workloads.yaml so they expose the required DB env names, redacted Secret reference, and HWLAB_CLOUD_DB_SSL_MODE=disable. Do not make cloud-api-db a hard readiness gate unless this repo also owns its Service/Endpoint rollout contract."
|
||||
});
|
||||
}
|
||||
|
||||
@@ -873,7 +874,7 @@ function validateCloudApiDbContract(reporter, deploy, workloads, services) {
|
||||
type: "runtime_blocker",
|
||||
scope: "cloud-api-db-health-gate",
|
||||
summary: `cloud-api DB runtime env is not ready; missing ${healthContract.missingEnv.join(", ")}.`,
|
||||
nextTask: "Configure DEV hwlab-cloud-api with Secret hwlab-cloud-api-dev-db/database-url and HWLAB_CLOUD_DB_SSL_MODE=require, then rerun health/preflight without printing the secret value."
|
||||
nextTask: "Configure DEV hwlab-cloud-api with Secret hwlab-cloud-api-dev-db/database-url and HWLAB_CLOUD_DB_SSL_MODE=disable, then rerun health/preflight without printing the secret value."
|
||||
});
|
||||
}
|
||||
|
||||
@@ -914,6 +915,13 @@ function inspectCloudApiDbStaticContract(deploy, workloads, services) {
|
||||
const missingSecretRefs = fields
|
||||
.filter((field) => field.secretRef && !field.secretRef.present)
|
||||
.map((field) => `${field.secretRef.secretName}/${field.secretRef.secretKey}`);
|
||||
const expectedSslMode = DEV_DB_ENV_CONTRACT.nonSecretDefaults.HWLAB_CLOUD_DB_SSL_MODE;
|
||||
const sslModeMatches =
|
||||
deployEnv.HWLAB_CLOUD_DB_SSL_MODE === expectedSslMode &&
|
||||
workloadEnv.get("HWLAB_CLOUD_DB_SSL_MODE")?.value === expectedSslMode;
|
||||
const mismatchedNonSecretEnv = sslModeMatches
|
||||
? []
|
||||
: [`HWLAB_CLOUD_DB_SSL_MODE must be ${expectedSslMode}`];
|
||||
const dnsContract = inspectCloudApiDbOptionalAliasContract(deployEnv, workloadEnv, services);
|
||||
|
||||
return {
|
||||
@@ -923,12 +931,14 @@ function inspectCloudApiDbStaticContract(deploy, workloads, services) {
|
||||
missingDeployEnv.length === 0 &&
|
||||
missingK8sEnv.length === 0 &&
|
||||
missingSecretRefs.length === 0 &&
|
||||
mismatchedNonSecretEnv.length === 0 &&
|
||||
dnsContract.ready,
|
||||
requiredEnv: fields,
|
||||
dns: dnsContract,
|
||||
missingDeployEnv,
|
||||
missingK8sEnv,
|
||||
missingSecretRefs,
|
||||
mismatchedNonSecretEnv,
|
||||
missingDnsContract: dnsContract.missing,
|
||||
secretMaterialRead: false,
|
||||
valuesRedacted: true,
|
||||
|
||||
@@ -15,7 +15,7 @@ test("source check is non-secret and does not attempt live DB access", async ()
|
||||
const report = await buildDevRuntimeMigrationReport(parseArgs(["--check"]), {
|
||||
env: {
|
||||
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:secret-password@db.example.test:5432/hwlab",
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "require"
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
||||
},
|
||||
now: () => "2026-05-22T00:00:00.000Z"
|
||||
});
|
||||
@@ -38,7 +38,7 @@ test("apply mode refuses missing DEV confirmation before touching query client",
|
||||
const report = await buildDevRuntimeMigrationReport(parseArgs(["--apply"]), {
|
||||
env: {
|
||||
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:redacted@example.invalid:5432/hwlab",
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "require"
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
||||
},
|
||||
queryClient,
|
||||
now: () => "2026-05-22T00:00:00.000Z"
|
||||
@@ -56,7 +56,7 @@ test("live dry-run separates migration ledger blocker from auth and schema", asy
|
||||
{
|
||||
env: {
|
||||
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:redacted@example.invalid:5432/hwlab",
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "require"
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
||||
},
|
||||
queryClient: createFakeQueryClient({ migrationReady: false }),
|
||||
now: () => "2026-05-22T00:00:00.000Z"
|
||||
@@ -81,7 +81,7 @@ test("apply mode records ledger and verifies durable runtime readiness", async (
|
||||
{
|
||||
env: {
|
||||
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:redacted@example.invalid:5432/hwlab",
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "require"
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
||||
},
|
||||
queryClient,
|
||||
now: () => "2026-05-22T00:00:00.000Z"
|
||||
@@ -112,7 +112,7 @@ test("live dry-run closes pg pool after readiness verification", async () => {
|
||||
{
|
||||
env: {
|
||||
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:redacted@example.invalid:5432/hwlab",
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "require"
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
||||
},
|
||||
pgModuleLoader: async () => createFakePgModule({ backingClient, pools }),
|
||||
now: () => "2026-05-22T00:00:00.000Z"
|
||||
@@ -133,7 +133,7 @@ test("apply mode uses a dedicated pg client transaction", async () => {
|
||||
{
|
||||
env: {
|
||||
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:redacted@example.invalid:5432/hwlab",
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "require"
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
||||
},
|
||||
pgModuleLoader: async () => createFakePgModule({ backingClient, pools }),
|
||||
now: () => "2026-05-22T00:00:00.000Z"
|
||||
|
||||
@@ -170,10 +170,19 @@ assert.equal(cloudApi.env.HWLAB_COMMIT_ID, deployManifest.commitId, "cloud-api h
|
||||
assert.equal(cloudApi.env.HWLAB_IMAGE, cloudApi.image, "cloud-api health image evidence");
|
||||
assert.equal(cloudApi.env.HWLAB_IMAGE_TAG, deployManifest.commitId.slice(0, 7), "cloud-api health image tag evidence");
|
||||
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_URL, "secretRef:hwlab-cloud-api-dev-db/database-url", "cloud-api DB URL must be a Secret reference placeholder");
|
||||
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_SSL_MODE, "require", "cloud-api DB SSL mode");
|
||||
assert.equal(
|
||||
cloudApi.env.HWLAB_CLOUD_DB_SSL_MODE,
|
||||
DEV_DB_ENV_CONTRACT.nonSecretDefaults.HWLAB_CLOUD_DB_SSL_MODE,
|
||||
"cloud-api DEV DB SSL mode"
|
||||
);
|
||||
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_CONTRACT, "dev-redacted-presence-only", "cloud-api DB contract marker");
|
||||
assert.equal(cloudApi.env.HWLAB_CLOUD_RUNTIME_ADAPTER, "postgres", "cloud-api runtime durable adapter");
|
||||
assert.equal(cloudApi.env.HWLAB_CLOUD_RUNTIME_DURABLE, "true", "cloud-api runtime durable flag");
|
||||
assert.equal(
|
||||
cloudApiWorkloadEnv.HWLAB_CLOUD_DB_SSL_MODE?.value,
|
||||
DEV_DB_ENV_CONTRACT.nonSecretDefaults.HWLAB_CLOUD_DB_SSL_MODE,
|
||||
"cloud-api workload DEV DB SSL mode"
|
||||
);
|
||||
assert.equal(cloudApiWorkloadEnv.HWLAB_CLOUD_RUNTIME_ADAPTER?.value, "postgres", "cloud-api workload runtime durable adapter");
|
||||
assert.equal(cloudApiWorkloadEnv.HWLAB_CLOUD_RUNTIME_DURABLE?.value, "true", "cloud-api workload runtime durable flag");
|
||||
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_SERVICE_NAME, undefined, "cloud-api DB alias service name is optional");
|
||||
|
||||
Reference in New Issue
Block a user