test: add dev m3 blocked evidence detail

This commit is contained in:
HWLAB Code Queue
2026-05-21 16:44:13 +00:00
parent 9f56de2062
commit 5e74278e74
4 changed files with 140 additions and 7 deletions
+68 -1
View File
@@ -2,7 +2,7 @@
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { randomUUID } from "node:crypto";
import { access, mkdir, writeFile } from "node:fs/promises";
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
import { request as httpRequest } from "node:http";
import { request as httpsRequest } from "node:https";
import path from "node:path";
@@ -14,6 +14,7 @@ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."
const defaultReportPath = "reports/dev-gate/dev-m3-hardware-loop.json";
const namespace = "hwlab-dev";
const issue = "pikasTech/HWLAB#38";
const deployWorkloadsPath = "deploy/k8s/base/workloads.yaml";
const serviceTargets = Object.freeze([
{
@@ -204,6 +205,70 @@ function collectLocalRuntimeObservations() {
return observations;
}
async function collectReadOnlySupplementalEvidence() {
const raw = await readFile(path.join(repoRoot, deployWorkloadsPath), "utf8");
const workloads = JSON.parse(raw);
const targets = ["hwlab-box-simu", "hwlab-gateway-simu", "hwlab-patch-panel"];
const observed = workloads.items
.filter((item) => targets.includes(item.metadata?.name))
.map((item) => {
const container = item.spec?.template?.spec?.containers?.[0] ?? {};
const env = Object.fromEntries((container.env ?? []).map((entry) => [entry.name, entry.value]));
return {
name: item.metadata.name,
kind: item.kind,
namespace: item.metadata.namespace,
replicas: item.spec?.replicas ?? null,
image: container.image,
env
};
});
const box = observed.find((item) => item.name === "hwlab-box-simu");
const gateway = observed.find((item) => item.name === "hwlab-gateway-simu");
const patchPanel = observed.find((item) => item.name === "hwlab-patch-panel");
const boxReadyForM3 = box?.replicas === 2 && String(box.env.HWLAB_BOX_ID ?? "").includes(",");
const gatewayReadyForM3 =
gateway?.replicas === 2 && String(gateway.env.HWLAB_BOX_RESOURCES ?? "").includes(",");
return [
{
id: "deploy-skeleton-m3-cardinality",
status: boxReadyForM3 && gatewayReadyForM3 ? "pass" : "gap",
blockerClass: boxReadyForM3 && gatewayReadyForM3 ? null : "runtime_blocker",
source: deployWorkloadsPath,
summary: boxReadyForM3 && gatewayReadyForM3
? "Checked-in DEV deploy skeleton declares two simulator identities for M3."
: "Read-only static comparison found the checked-in DEV deploy skeleton declares one box simulator replica and one gateway simulator replica; proving two of each in live DEV requires a separate deploy/runtime task or authorized runtime observation.",
evidence: {
required: {
"hwlab-box-simu": 2,
"hwlab-gateway-simu": 2,
"hwlab-patch-panel": 1,
wiring: "box-simu-1 DO1 -> box-simu-2 DI1"
},
observed: {
"hwlab-box-simu": {
replicas: box?.replicas ?? null,
env: box?.env ?? null
},
"hwlab-gateway-simu": {
replicas: gateway?.replicas ?? null,
env: gateway?.env ?? null
},
"hwlab-patch-panel": {
replicas: patchPanel?.replicas ?? null,
env: patchPanel?.env ?? null
}
}
},
requiredFollowUp: boxReadyForM3 && gatewayReadyForM3
? "None from static deploy cardinality."
: "Create a separate authorized DEV deploy/runtime task to provision or expose two box-simu instances, two gateway-simu instances, M3 wiring, and audit/evidence endpoints; do not perform that mutation in this read-only continuation."
}
];
}
async function probeJson(url, options = {}) {
const method = options.method ?? "GET";
const startedAt = isoNow();
@@ -533,6 +598,7 @@ function baseReport({ commitId, observedAt }) {
evidence: [DEV_ENDPOINT]
}
],
readOnlySupplementalEvidence: [],
localRuntimeObservations: [],
liveOperation: {
status: "not_run",
@@ -591,6 +657,7 @@ async function main() {
commitId: currentCommit(),
observedAt
});
report.readOnlySupplementalEvidence = await collectReadOnlySupplementalEvidence();
const ingress = await observeDevIngress();
report.localRuntimeObservations = collectLocalRuntimeObservations();
+20
View File
@@ -339,6 +339,26 @@ async function validateDevM3Report(report, label) {
}
}
if (Object.hasOwn(report, "readOnlySupplementalEvidence")) {
assertArray(report.readOnlySupplementalEvidence, `${label}.readOnlySupplementalEvidence`);
for (const [index, evidence] of report.readOnlySupplementalEvidence.entries()) {
const evidenceLabel = `${label}.readOnlySupplementalEvidence[${index}]`;
assertObject(evidence, evidenceLabel);
for (const field of ["id", "status", "source", "summary", "evidence", "requiredFollowUp"]) {
assert.ok(Object.hasOwn(evidence, field), `${evidenceLabel} missing ${field}`);
}
assertString(evidence.id, `${evidenceLabel}.id`);
assert.ok(["pass", "gap"].includes(evidence.status), `${evidenceLabel}.status must be pass or gap`);
assertRepoRelativePath(evidence.source, `${evidenceLabel}.source`);
assertString(evidence.summary, `${evidenceLabel}.summary`);
assertObject(evidence.evidence, `${evidenceLabel}.evidence`);
assertString(evidence.requiredFollowUp, `${evidenceLabel}.requiredFollowUp`);
if (Object.hasOwn(evidence, "blockerClass") && evidence.blockerClass !== null) {
assert.ok(blockerTypes.has(evidence.blockerClass), `${evidenceLabel}.blockerClass must be known`);
}
}
}
assertObject(report.liveOperation, `${label}.liveOperation`);
assertStatus(report.liveOperation.status, `${label}.liveOperation.status`);
for (const field of ["operationId", "traceId", "auditId", "evidenceId", "summary"]) {