fix: classify dev edge DB blockers
This commit is contained in:
@@ -15,6 +15,12 @@ const publicHost = "74.48.78.17";
|
||||
const publicPort = 16667;
|
||||
const namespace = "hwlab-dev";
|
||||
const tcpPorts = [16667, 7000, 7402];
|
||||
const CODE_AGENT_PROVIDER_SECRET_CONTRACT = Object.freeze({
|
||||
sourceIssue: "pikasTech/HWLAB#143",
|
||||
env: "OPENAI_API_KEY",
|
||||
secretName: "hwlab-code-agent-provider",
|
||||
secretKey: "openai-api-key"
|
||||
});
|
||||
const runtimeServices = [
|
||||
{ serviceId: "hwlab-cloud-api", port: 6667 },
|
||||
{ serviceId: "hwlab-router", port: 7401 },
|
||||
@@ -69,11 +75,24 @@ export async function runDevEdgeHealthSmoke(argv) {
|
||||
};
|
||||
edgeHealth.runtimeDbReadiness = inspectRuntimeDbReadiness(edgeHealth);
|
||||
Object.assign(edgeHealth, classify(edgeHealth));
|
||||
edgeHealth.providerSecretReadiness = inspectProviderSecretReadiness(edgeHealth);
|
||||
edgeHealth.parallelBlockers = classifyParallelBlockers(edgeHealth);
|
||||
if (edgeHealth.status === "pass" && edgeHealth.parallelBlockers.length > 0) {
|
||||
Object.assign(edgeHealth, classifyParallelBlockerStatus(edgeHealth.parallelBlockers));
|
||||
}
|
||||
edgeHealth.milestoneImpact = classifyMilestoneImpact(edgeHealth);
|
||||
const report = await createDevGateReport(edgeHealth);
|
||||
await maybeWriteReport(report, args);
|
||||
return { report, exitCode: edgeHealth.status === "pass" ? 0 : 2 };
|
||||
}
|
||||
|
||||
export const __testHooks = Object.freeze({
|
||||
classifyMilestoneImpact,
|
||||
classifyParallelBlockers,
|
||||
inspectProviderSecretReadiness,
|
||||
summarizeRuntimeDbReadiness
|
||||
});
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = {
|
||||
live: false,
|
||||
@@ -106,8 +125,7 @@ function nowIso() {
|
||||
async function createDevGateReport(edgeHealth) {
|
||||
const commitId = await gitCommitId();
|
||||
const status = edgeHealth.status === "pass" ? "pass" : edgeHealth.status === "not_run" ? "not_run" : "blocked";
|
||||
const blockerType = status === "blocked" ? blockerTypeFor(edgeHealth.classification) : null;
|
||||
const blockerScope = edgeHealth.diagnosis?.likelyLayer ?? edgeHealth.classification;
|
||||
const blockers = reportBlockers(edgeHealth, status);
|
||||
|
||||
return {
|
||||
$schema: "https://hwlab.pikastech.local/schemas/dev-gate-report.schema.json",
|
||||
@@ -140,6 +158,7 @@ async function createDevGateReport(edgeHealth) {
|
||||
"node scripts/validate-dev-gate-report.mjs",
|
||||
"node --check scripts/dev-edge-health-smoke.mjs",
|
||||
"node --check scripts/src/dev-edge-health-smoke-lib.mjs",
|
||||
"node --test scripts/src/dev-edge-health-smoke-lib.test.mjs",
|
||||
"node scripts/dev-edge-health-smoke.mjs --live --write-report"
|
||||
],
|
||||
localSmoke: {
|
||||
@@ -172,16 +191,7 @@ async function createDevGateReport(edgeHealth) {
|
||||
],
|
||||
summary: edgeHealth.blocker ?? "DEV edge health preconditions are satisfied."
|
||||
},
|
||||
blockers: blockerType
|
||||
? [
|
||||
{
|
||||
type: blockerType,
|
||||
scope: blockerScope,
|
||||
status: "open",
|
||||
summary: edgeHealth.blocker
|
||||
}
|
||||
]
|
||||
: [],
|
||||
blockers,
|
||||
edgeHealth
|
||||
};
|
||||
}
|
||||
@@ -204,6 +214,34 @@ function blockerTypeFor(classification) {
|
||||
return "network_blocker";
|
||||
}
|
||||
|
||||
function reportBlockers(edgeHealth, status) {
|
||||
if (status !== "blocked") {
|
||||
return [];
|
||||
}
|
||||
|
||||
const parallelBlockers = Array.isArray(edgeHealth.parallelBlockers) ? edgeHealth.parallelBlockers : [];
|
||||
if (parallelBlockers.length > 0) {
|
||||
return parallelBlockers.map(({ type, scope, status: blockerStatus, summary, classification, sourceIssue, impact }) => ({
|
||||
type,
|
||||
scope,
|
||||
status: blockerStatus,
|
||||
summary,
|
||||
classification,
|
||||
sourceIssue,
|
||||
impact
|
||||
}));
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
type: blockerTypeFor(edgeHealth.classification),
|
||||
scope: edgeHealth.diagnosis?.likelyLayer ?? edgeHealth.classification,
|
||||
status: "open",
|
||||
summary: edgeHealth.blocker
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function requireDevEndpoint() {
|
||||
const endpoint = new URL(DEV_ENDPOINT);
|
||||
assert.equal(endpoint.protocol, "http:", "DEV endpoint must use http");
|
||||
@@ -246,7 +284,8 @@ async function inspectContracts() {
|
||||
serviceCount: deploy.services?.length ?? 0,
|
||||
edgeProxy: deploy.services?.find((service) => service.serviceId === "hwlab-edge-proxy") ?? null,
|
||||
tunnelClient: deploy.services?.find((service) => service.serviceId === "hwlab-tunnel-client") ?? null,
|
||||
cloudApiDb: inspectCloudApiDbContract(deploy)
|
||||
cloudApiDb: inspectCloudApiDbContract(deploy),
|
||||
codeAgentProvider: inspectCodeAgentProviderContract(deploy)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -322,6 +361,38 @@ function inspectCloudApiDbContract(deploy) {
|
||||
};
|
||||
}
|
||||
|
||||
function inspectCodeAgentProviderContract(deploy) {
|
||||
const env = deploy.services?.find((service) => service.serviceId === "hwlab-cloud-api")?.env ?? {};
|
||||
const expectedRef = `secretRef:${CODE_AGENT_PROVIDER_SECRET_CONTRACT.secretName}/${CODE_AGENT_PROVIDER_SECRET_CONTRACT.secretKey}`;
|
||||
const secretRefPresent = env[CODE_AGENT_PROVIDER_SECRET_CONTRACT.env] === expectedRef;
|
||||
return {
|
||||
status: secretRefPresent ? "degraded" : "blocked",
|
||||
sourceIssue: CODE_AGENT_PROVIDER_SECRET_CONTRACT.sourceIssue,
|
||||
requiredEnv: [
|
||||
{
|
||||
name: CODE_AGENT_PROVIDER_SECRET_CONTRACT.env,
|
||||
present: Boolean(env[CODE_AGENT_PROVIDER_SECRET_CONTRACT.env]),
|
||||
redacted: true,
|
||||
source: "k8s-secret-ref"
|
||||
}
|
||||
],
|
||||
secretRef: {
|
||||
env: CODE_AGENT_PROVIDER_SECRET_CONTRACT.env,
|
||||
secretName: CODE_AGENT_PROVIDER_SECRET_CONTRACT.secretName,
|
||||
secretKey: CODE_AGENT_PROVIDER_SECRET_CONTRACT.secretKey,
|
||||
present: secretRefPresent,
|
||||
envInjected: false,
|
||||
secretPresent: "not_observed_by_manifest",
|
||||
secretKeyPresent: secretRefPresent ? "declared" : "not_observed_by_manifest",
|
||||
secretValueRead: false,
|
||||
redacted: true
|
||||
},
|
||||
secretMaterialRead: false,
|
||||
valuesRedacted: true,
|
||||
note: "Manifest-only Code Agent provider Secret reference; this is not runtime Secret injection or provider connectivity evidence."
|
||||
};
|
||||
}
|
||||
|
||||
function notRunDbReadiness(blockerMessage) {
|
||||
return {
|
||||
status: "not_run",
|
||||
@@ -330,6 +401,7 @@ function notRunDbReadiness(blockerMessage) {
|
||||
secret: unknownDbSecretPresence("not_observed"),
|
||||
connectionAttempted: false,
|
||||
connectionResult: "not_run",
|
||||
connectionClassification: "not_run",
|
||||
liveConnected: false,
|
||||
valuesRedacted: true,
|
||||
secretMaterialRead: false,
|
||||
@@ -359,6 +431,7 @@ function summarizeRuntimeDbReadiness(db, secretPresence = unknownDbSecretPresenc
|
||||
secret: secretPresence,
|
||||
connectionAttempted: false,
|
||||
connectionResult: "health_db_missing",
|
||||
connectionClassification: "health_db_missing",
|
||||
liveConnected: false,
|
||||
valuesRedacted: true,
|
||||
secretMaterialRead: false,
|
||||
@@ -374,6 +447,8 @@ function summarizeRuntimeDbReadiness(db, secretPresence = unknownDbSecretPresenc
|
||||
);
|
||||
const connectionAttempted = Boolean(db.connectionAttempted ?? db.connectionChecked);
|
||||
const connectionResult = db.connectionResult ?? db.connection?.result ?? (connectionAttempted ? "unknown" : "not_attempted");
|
||||
const connectionClassification =
|
||||
db.connection?.classification ?? classificationForRuntimeDb(db, connectionAttempted, connectionResult);
|
||||
const liveConnected = Boolean(db.liveConnected ?? db.connected ?? db.ready);
|
||||
const blockerMessage = liveConnected ? null : db.blocker ?? blockerForRuntimeDb(db, connectionAttempted, connectionResult);
|
||||
const status = liveConnected ? "pass" : "blocked";
|
||||
@@ -393,6 +468,7 @@ function summarizeRuntimeDbReadiness(db, secretPresence = unknownDbSecretPresenc
|
||||
},
|
||||
connectionAttempted,
|
||||
connectionResult,
|
||||
connectionClassification,
|
||||
liveConnected,
|
||||
valuesRedacted: db.redaction?.valuesRedacted !== false && db.safety?.valuesRedacted === true,
|
||||
secretMaterialRead: false,
|
||||
@@ -404,6 +480,7 @@ function summarizeRuntimeDbReadiness(db, secretPresence = unknownDbSecretPresenc
|
||||
missingEnv: Array.isArray(db.missingEnv) ? db.missingEnv : [],
|
||||
connectionAttempted,
|
||||
connectionResult,
|
||||
classification: connectionClassification,
|
||||
liveConnected,
|
||||
endpointRedacted: db.connection?.endpointRedacted !== false,
|
||||
valueRedacted: true
|
||||
@@ -412,6 +489,34 @@ function summarizeRuntimeDbReadiness(db, secretPresence = unknownDbSecretPresenc
|
||||
};
|
||||
}
|
||||
|
||||
function classificationForRuntimeDb(db, connectionAttempted, connectionResult) {
|
||||
if (typeof db?.connectionClassification === "string") {
|
||||
return db.connectionClassification;
|
||||
}
|
||||
if (typeof db?.classification === "string") {
|
||||
return db.classification;
|
||||
}
|
||||
if (db?.configReady !== true) {
|
||||
return "missing_runtime_env";
|
||||
}
|
||||
if (!connectionAttempted) {
|
||||
return "configured_no_live_attempt";
|
||||
}
|
||||
if (connectionResult === "dns_error") {
|
||||
return "dns_resolution_failed";
|
||||
}
|
||||
if (connectionResult === "refused") {
|
||||
return "tcp_refused";
|
||||
}
|
||||
if (connectionResult === "timeout") {
|
||||
return "tcp_timeout";
|
||||
}
|
||||
if (connectionResult === "network_unreachable") {
|
||||
return "network_unreachable";
|
||||
}
|
||||
return connectionResult === "connected" ? "tcp_connected" : "tcp_error";
|
||||
}
|
||||
|
||||
function blockerForRuntimeDb(db, connectionAttempted, connectionResult) {
|
||||
if (db.configReady !== true) {
|
||||
const missing = Array.isArray(db.missingEnv) && db.missingEnv.length > 0 ? db.missingEnv.join(", ") : "required DB env";
|
||||
@@ -436,6 +541,18 @@ function unknownDbSecretPresence(state) {
|
||||
};
|
||||
}
|
||||
|
||||
function unknownProviderSecretPresence(state) {
|
||||
return {
|
||||
observable: false,
|
||||
secretName: CODE_AGENT_PROVIDER_SECRET_CONTRACT.secretName,
|
||||
secretKey: CODE_AGENT_PROVIDER_SECRET_CONTRACT.secretKey,
|
||||
secretPresent: state,
|
||||
secretKeyPresent: state,
|
||||
secretValueRead: false,
|
||||
redacted: true
|
||||
};
|
||||
}
|
||||
|
||||
async function inspectKubernetes() {
|
||||
const hasKubectl = await commandExists("kubectl");
|
||||
const result = {
|
||||
@@ -446,6 +563,7 @@ async function inspectKubernetes() {
|
||||
endpoints: [],
|
||||
pods: [],
|
||||
dbSecret: unknownDbSecretPresence("not_observed"),
|
||||
codeAgentProviderSecret: unknownProviderSecretPresence("not_observed"),
|
||||
notes: []
|
||||
};
|
||||
if (!hasKubectl) {
|
||||
@@ -464,6 +582,7 @@ async function inspectKubernetes() {
|
||||
"-n", namespace, "get", "pods", "-l", "hwlab.pikastech.local/profile=dev", "-o", "json"
|
||||
]);
|
||||
await collectDbSecretPresence(result);
|
||||
await collectCodeAgentProviderSecretPresence(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -551,6 +670,48 @@ async function collectDbSecretPresence(result) {
|
||||
}
|
||||
}
|
||||
|
||||
async function collectCodeAgentProviderSecretPresence(result) {
|
||||
const ref = CODE_AGENT_PROVIDER_SECRET_CONTRACT;
|
||||
const base = {
|
||||
observable: true,
|
||||
sourceIssue: ref.sourceIssue,
|
||||
env: ref.env,
|
||||
secretName: ref.secretName,
|
||||
secretKey: ref.secretKey,
|
||||
secretPresent: false,
|
||||
secretKeyPresent: false,
|
||||
secretValueRead: false,
|
||||
redacted: true
|
||||
};
|
||||
const exists = await runCommand("kubectl", ["-n", namespace, "get", "secret", ref.secretName, "-o", "name"], {
|
||||
timeoutMs: 5000
|
||||
});
|
||||
if (exists.exitCode !== 0) {
|
||||
result.codeAgentProviderSecret = {
|
||||
...base,
|
||||
command: "kubectl get secret -o name",
|
||||
error: exists.stderr.trim() || "secret not observed"
|
||||
};
|
||||
result.notes.push(`kubectl Code Agent provider Secret presence probe failed: ${exists.stderr.trim()}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const keyPresence = await runCommand("kubectl", ["-n", namespace, "describe", "secret", ref.secretName], {
|
||||
timeoutMs: 5000
|
||||
});
|
||||
const keyLinePattern = new RegExp(`^\\s*${escapeRegExp(ref.secretKey)}:\\s+\\d+\\s+bytes\\s*$`, "mu");
|
||||
result.codeAgentProviderSecret = {
|
||||
...base,
|
||||
secretPresent: true,
|
||||
secretKeyPresent: keyPresence.exitCode === 0 && keyLinePattern.test(keyPresence.stdout),
|
||||
command: "kubectl describe secret <name> (key-presence-only)",
|
||||
error: keyPresence.exitCode === 0 ? null : keyPresence.stderr.trim()
|
||||
};
|
||||
if (keyPresence.exitCode !== 0) {
|
||||
result.notes.push(`kubectl Code Agent provider Secret key presence probe failed: ${keyPresence.stderr.trim()}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function inspectClusterDns() {
|
||||
const results = [];
|
||||
for (const service of runtimeServices) {
|
||||
@@ -615,6 +776,131 @@ function classifyReachableHealth(json, runtimeDb = null) {
|
||||
return { status: "pass", classification: "none", blocker: null };
|
||||
}
|
||||
|
||||
function inspectProviderSecretReadiness(edgeHealth) {
|
||||
const secret = edgeHealth.kubernetes?.codeAgentProviderSecret ?? unknownProviderSecretPresence("not_observed");
|
||||
const manifest = edgeHealth.contracts?.deploy?.codeAgentProvider ?? null;
|
||||
const manifestRefPresent = manifest?.secretRef?.present === true;
|
||||
const secretPresent = secret.secretPresent === true;
|
||||
const secretKeyPresent = secret.secretKeyPresent === true;
|
||||
const status = secret.observable && (!secretPresent || !secretKeyPresent) ? "blocked" : "not_proven";
|
||||
const classification = status === "blocked" ? "provider_secret_missing" : "provider_secret_presence_not_proven";
|
||||
return {
|
||||
status,
|
||||
sourceIssue: CODE_AGENT_PROVIDER_SECRET_CONTRACT.sourceIssue,
|
||||
provider: "openai-responses",
|
||||
requiredEnv: CODE_AGENT_PROVIDER_SECRET_CONTRACT.env,
|
||||
manifestRefPresent,
|
||||
secret: {
|
||||
...secret,
|
||||
secretValueRead: false,
|
||||
redacted: true
|
||||
},
|
||||
providerCallAttempted: false,
|
||||
providerConnected: false,
|
||||
secretMaterialRead: false,
|
||||
valuesRedacted: true,
|
||||
classification,
|
||||
blocker: status === "blocked"
|
||||
? `Code Agent provider Secret ${CODE_AGENT_PROVIDER_SECRET_CONTRACT.secretName}/${CODE_AGENT_PROVIDER_SECRET_CONTRACT.secretKey} is not present as key-presence evidence; #143 real provider-backed chat remains blocked`
|
||||
: "Code Agent provider Secret presence was not proven by this edge-health run",
|
||||
evidence: [
|
||||
{
|
||||
source: secret.observable ? "kubernetes-secret-presence" : "runtime-not-observable",
|
||||
manifestRefPresent,
|
||||
secretPresent,
|
||||
secretKeyPresent,
|
||||
secretValueRead: false,
|
||||
redacted: true
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function classifyParallelBlockers(edgeHealth) {
|
||||
const blockers = [];
|
||||
const dbReadiness = edgeHealth.runtimeDbReadiness;
|
||||
if (dbReadiness?.status === "blocked") {
|
||||
blockers.push({
|
||||
id: "cloud-api-db-live",
|
||||
type: "runtime_blocker",
|
||||
scope: "cloud-api-db",
|
||||
status: "open",
|
||||
sourceIssue: "pikasTech/HWLAB#49",
|
||||
classification: dbReadiness.connectionClassification ?? dbReadiness.connectionResult,
|
||||
summary: dbReadiness.blocker,
|
||||
impact: {
|
||||
blocks: ["DB live readiness", "M4 live agent scheduling precondition", "M5 DEV-LIVE acceptance"],
|
||||
separateFrom: ["pikasTech/HWLAB#143 provider Secret", "M3 hardware trusted-loop proof"]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const provider = edgeHealth.providerSecretReadiness;
|
||||
if (provider?.status === "blocked") {
|
||||
blockers.push({
|
||||
id: "code-agent-provider-secret",
|
||||
type: "agent_blocker",
|
||||
scope: "code-agent-provider-secret",
|
||||
status: "open",
|
||||
sourceIssue: CODE_AGENT_PROVIDER_SECRET_CONTRACT.sourceIssue,
|
||||
classification: provider.classification,
|
||||
summary: provider.blocker,
|
||||
impact: {
|
||||
blocks: ["#143 provider-backed Code Agent chat", "M4 provider-backed agent execution", "M5 DEV-LIVE acceptance"],
|
||||
separateFrom: ["pikasTech/HWLAB#49 DB live readiness", "M3 hardware trusted-loop proof"]
|
||||
}
|
||||
});
|
||||
}
|
||||
return blockers;
|
||||
}
|
||||
|
||||
function classifyParallelBlockerStatus(parallelBlockers) {
|
||||
return blocker(
|
||||
"parallel_runtime_blocker",
|
||||
parallelBlockers.map((item) => item.summary).join("; "),
|
||||
{
|
||||
likelyLayer: "parallel-dev-blockers",
|
||||
confidence: "high",
|
||||
likelyCause: "one or more independent DEV blockers remain open after route health passed",
|
||||
evidence: parallelBlockers.map(({ id, scope, classification, sourceIssue }) => ({
|
||||
id,
|
||||
scope,
|
||||
classification,
|
||||
sourceIssue
|
||||
})),
|
||||
notProven: ["DB-backed runtime readiness", "provider-backed Code Agent readiness", "M3/M4/M5 DEV-LIVE acceptance"],
|
||||
nextTask: "Close each independent blocker with redacted evidence, then rerun the read-only edge smoke."
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function classifyMilestoneImpact(edgeHealth) {
|
||||
const dbBlocked = edgeHealth.runtimeDbReadiness?.status === "blocked";
|
||||
const providerBlocked = edgeHealth.providerSecretReadiness?.status === "blocked";
|
||||
return {
|
||||
M3: {
|
||||
status: "separate_blocker",
|
||||
summary: "M3 still requires real DEV hardware trusted-loop operation/trace/audit/evidence; DB DNS failure and #143 provider Secret absence do not prove or clear M3."
|
||||
},
|
||||
M4: {
|
||||
status: dbBlocked || providerBlocked ? "blocked" : "not_proven",
|
||||
blockedBy: [
|
||||
...(dbBlocked ? ["cloud-api-db-live"] : []),
|
||||
...(providerBlocked ? ["code-agent-provider-secret"] : [])
|
||||
],
|
||||
summary: "M4 live agent-loop remains blocked until DB live readiness and provider-backed agent prerequisites are independently green."
|
||||
},
|
||||
M5: {
|
||||
status: dbBlocked || providerBlocked ? "blocked" : "not_proven",
|
||||
blockedBy: [
|
||||
...(dbBlocked ? ["cloud-api-db-live"] : []),
|
||||
...(providerBlocked ? ["code-agent-provider-secret"] : [])
|
||||
],
|
||||
summary: "M5 DEV-LIVE acceptance cannot be promoted from route health, env presence, or provider manifest refs while DB live and #143 provider Secret blockers remain open."
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function classifyRefusedPort(publicEdgePort, frpsControl, tunnelHealth) {
|
||||
if (frpsControl?.status === "error" && frpsControl.code === "ECONNREFUSED") {
|
||||
const allPortsRefused = tunnelHealth?.status === "error" && tunnelHealth.code === "ECONNREFUSED";
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { __testHooks } from "./dev-edge-health-smoke-lib.mjs";
|
||||
|
||||
test("runtime DB readiness preserves dns resolution classification", () => {
|
||||
const readiness = __testHooks.summarizeRuntimeDbReadiness(
|
||||
{
|
||||
configReady: true,
|
||||
liveConnected: false,
|
||||
liveDbEvidence: false,
|
||||
connectionAttempted: true,
|
||||
connectionResult: "dns_error",
|
||||
fields: [
|
||||
{ name: "HWLAB_CLOUD_DB_URL", present: true },
|
||||
{ name: "HWLAB_CLOUD_DB_SSL_MODE", present: true }
|
||||
],
|
||||
secretRefs: [
|
||||
{
|
||||
env: "HWLAB_CLOUD_DB_URL",
|
||||
secretName: "hwlab-cloud-api-dev-db",
|
||||
secretKey: "database-url",
|
||||
present: true,
|
||||
envInjected: true
|
||||
}
|
||||
],
|
||||
connection: {
|
||||
attempted: true,
|
||||
result: "dns_error",
|
||||
classification: "dns_resolution_failed",
|
||||
endpointRedacted: true,
|
||||
valueRedacted: true
|
||||
},
|
||||
redaction: { valuesRedacted: true },
|
||||
safety: { valuesRedacted: true, liveDbEvidence: false },
|
||||
blocker: "DB endpoint DNS resolution failed from the cloud-api runtime"
|
||||
},
|
||||
{
|
||||
observable: true,
|
||||
secretName: "hwlab-cloud-api-dev-db",
|
||||
secretKey: "database-url",
|
||||
secretPresent: true,
|
||||
secretKeyPresent: true,
|
||||
secretValueRead: false,
|
||||
redacted: true
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(readiness.status, "blocked");
|
||||
assert.equal(readiness.connectionResult, "dns_error");
|
||||
assert.equal(readiness.connectionClassification, "dns_resolution_failed");
|
||||
assert.equal(readiness.liveConnected, false);
|
||||
assert.equal(readiness.liveDbEvidence, false);
|
||||
assert.equal(readiness.secretMaterialRead, false);
|
||||
assert.equal(readiness.evidence[0].classification, "dns_resolution_failed");
|
||||
});
|
||||
|
||||
test("parallel blockers keep DB DNS and provider Secret gaps independent", () => {
|
||||
const edgeHealth = {
|
||||
runtimeDbReadiness: {
|
||||
status: "blocked",
|
||||
connectionResult: "dns_error",
|
||||
connectionClassification: "dns_resolution_failed",
|
||||
blocker: "DB endpoint DNS resolution failed from the cloud-api runtime"
|
||||
},
|
||||
providerSecretReadiness: {
|
||||
status: "blocked",
|
||||
classification: "provider_secret_missing",
|
||||
blocker: "Code Agent provider Secret hwlab-code-agent-provider/openai-api-key is not present as key-presence evidence"
|
||||
}
|
||||
};
|
||||
|
||||
const blockers = __testHooks.classifyParallelBlockers(edgeHealth);
|
||||
assert.deepEqual(
|
||||
blockers.map((blocker) => blocker.scope),
|
||||
["cloud-api-db", "code-agent-provider-secret"]
|
||||
);
|
||||
assert.equal(blockers[0].classification, "dns_resolution_failed");
|
||||
assert.equal(blockers[0].sourceIssue, "pikasTech/HWLAB#49");
|
||||
assert.equal(blockers[1].classification, "provider_secret_missing");
|
||||
assert.equal(blockers[1].sourceIssue, "pikasTech/HWLAB#143");
|
||||
assert.ok(blockers[0].impact.separateFrom.includes("pikasTech/HWLAB#143 provider Secret"));
|
||||
assert.ok(blockers[1].impact.separateFrom.includes("pikasTech/HWLAB#49 DB live readiness"));
|
||||
});
|
||||
|
||||
test("provider Secret blocker remains open even if DB is not blocked", () => {
|
||||
const blockers = __testHooks.classifyParallelBlockers({
|
||||
runtimeDbReadiness: { status: "pass" },
|
||||
providerSecretReadiness: {
|
||||
status: "blocked",
|
||||
classification: "provider_secret_missing",
|
||||
blocker: "Code Agent provider Secret hwlab-code-agent-provider/openai-api-key is not present as key-presence evidence"
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(blockers.length, 1);
|
||||
assert.equal(blockers[0].scope, "code-agent-provider-secret");
|
||||
assert.equal(blockers[0].type, "agent_blocker");
|
||||
});
|
||||
|
||||
test("milestone impact does not promote DB or provider evidence into M3/M4/M5", () => {
|
||||
const impact = __testHooks.classifyMilestoneImpact({
|
||||
runtimeDbReadiness: { status: "blocked" },
|
||||
providerSecretReadiness: { status: "blocked" }
|
||||
});
|
||||
|
||||
assert.equal(impact.M3.status, "separate_blocker");
|
||||
assert.deepEqual(impact.M4.blockedBy, ["cloud-api-db-live", "code-agent-provider-secret"]);
|
||||
assert.deepEqual(impact.M5.blockedBy, ["cloud-api-db-live", "code-agent-provider-secret"]);
|
||||
assert.match(impact.M5.summary, /cannot be promoted/u);
|
||||
});
|
||||
Reference in New Issue
Block a user