test: add dev m3 hardware loop smoke

This commit is contained in:
HWLAB Code Queue
2026-05-21 16:39:29 +00:00
parent 1e8d009e95
commit f1367da333
4 changed files with 1089 additions and 0 deletions
+162
View File
@@ -20,6 +20,15 @@ const requiredDocs = [
"docs/dev-acceptance-matrix.md",
"docs/m0-contract-audit.md"
];
const requiredDevM3Issue = "pikasTech/HWLAB#38";
const requiredDevM3ValidationCommands = [
"node --check scripts/dev-m3-hardware-loop-smoke.mjs",
"node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --confirmed-non-production"
];
const requiredDevM3Docs = [
"docs/dev-acceptance-matrix.md",
"docs/m3-hardware-loop.md"
];
const statusValues = new Set(["pass", "blocked", "not_run", "not_applicable", "failed"]);
const blockerTypes = new Set([
@@ -52,6 +61,11 @@ function assertStatus(value, label) {
assert.ok(statusValues.has(value), `${label} must be one of ${Array.from(statusValues).join(", ")}`);
}
function assertTimestamp(value, label) {
assertString(value, label);
assert.ok(!Number.isNaN(Date.parse(value)), `${label} must be an RFC 3339 timestamp`);
}
function assertRepoRelativePath(value, label) {
assertString(value, label);
assert.ok(!path.isAbsolute(value), `${label} must be repo-relative`);
@@ -96,6 +110,11 @@ async function validateReport(relativePath) {
const label = relativePath;
assertObject(report, label);
if (report.issue === requiredDevM3Issue || report.taskId === "dev-m3-hardware-loop") {
await validateDevM3Report(report, label);
return;
}
for (const field of [
"$schema",
"$id",
@@ -206,6 +225,149 @@ async function validateReport(relativePath) {
}
}
async function validateDevM3Report(report, label) {
for (const field of [
"$schema",
"$id",
"reportVersion",
"issue",
"taskId",
"commitId",
"acceptanceLevel",
"devOnly",
"prodDisabled",
"sourceContract",
"validationCommands",
"runtimeTarget",
"safetyGates",
"liveChecks",
"liveOperation",
"blockers",
"summary"
]) {
assert.ok(Object.hasOwn(report, field), `${label} missing ${field}`);
}
assertString(report.$schema, `${label}.$schema`);
assertString(report.$id, `${label}.$id`);
assert.equal(report.reportVersion, "v1", `${label}.reportVersion`);
assert.equal(report.issue, requiredDevM3Issue, `${label}.issue`);
assert.equal(report.taskId, "dev-m3-hardware-loop", `${label}.taskId`);
assert.match(report.commitId, /^([a-f0-9]{7,40}|unknown)$/, `${label}.commitId`);
assert.equal(report.acceptanceLevel, "dev_m3_hardware_loop", `${label}.acceptanceLevel`);
assert.equal(report.devOnly, true, `${label}.devOnly`);
assert.equal(report.prodDisabled, true, `${label}.prodDisabled`);
assertObject(report.sourceContract, `${label}.sourceContract`);
assertStatus(report.sourceContract.status, `${label}.sourceContract.status`);
assertStringArray(report.sourceContract.documents, `${label}.sourceContract.documents`, {
minLength: requiredDevM3Docs.length
});
assertString(report.sourceContract.summary, `${label}.sourceContract.summary`);
const documents = new Set(report.sourceContract.documents);
assertUnique(report.sourceContract.documents, `${label}.sourceContract.documents`);
for (const requiredDoc of requiredDevM3Docs) {
assert.ok(documents.has(requiredDoc), `${label}.sourceContract.documents missing ${requiredDoc}`);
}
for (const [index, documentPath] of report.sourceContract.documents.entries()) {
const absolutePath = assertRepoRelativePath(
documentPath,
`${label}.sourceContract.documents[${index}]`
);
await access(absolutePath, fsConstants.R_OK);
}
assertStringArray(report.validationCommands, `${label}.validationCommands`, { minLength: 2 });
assertUnique(report.validationCommands, `${label}.validationCommands`);
for (const requiredCommand of requiredDevM3ValidationCommands) {
assert.ok(
report.validationCommands.includes(requiredCommand),
`${label}.validationCommands missing ${requiredCommand}`
);
}
assertObject(report.runtimeTarget, `${label}.runtimeTarget`);
assert.equal(report.runtimeTarget.endpoint, "http://74.48.78.17:6667", `${label}.runtimeTarget.endpoint`);
assert.equal(report.runtimeTarget.namespace, "hwlab-dev", `${label}.runtimeTarget.namespace`);
assert.equal(report.runtimeTarget.environment, "dev", `${label}.runtimeTarget.environment`);
assert.equal(report.runtimeTarget.requiredBoxSimulators, 2, `${label}.runtimeTarget.requiredBoxSimulators`);
assert.equal(report.runtimeTarget.requiredGatewaySimulators, 2, `${label}.runtimeTarget.requiredGatewaySimulators`);
assert.equal(report.runtimeTarget.realHardwareAllowed, false, `${label}.runtimeTarget.realHardwareAllowed`);
assert.equal(report.runtimeTarget.prodAllowed, false, `${label}.runtimeTarget.prodAllowed`);
assertObject(report.safetyGates, `${label}.safetyGates`);
for (const field of [
"liveFlagRequired",
"confirmDevRequired",
"confirmedNonProductionRequired",
"prodForbidden",
"realHardwareForbidden",
"secretReadForbidden",
"forcePushForbidden",
"unideskRuntimeSubstitutionForbidden"
]) {
assert.equal(report.safetyGates[field], true, `${label}.safetyGates.${field}`);
}
assertArray(report.liveChecks, `${label}.liveChecks`);
assert.ok(report.liveChecks.length >= 8, `${label}.liveChecks must include DEV M3 checks`);
assertUnique(report.liveChecks.map((check) => check.id), `${label}.liveChecks`);
for (const [index, check] of report.liveChecks.entries()) {
const checkLabel = `${label}.liveChecks[${index}]`;
assertObject(check, checkLabel);
for (const field of ["id", "status", "summary", "evidence"]) {
assert.ok(Object.hasOwn(check, field), `${checkLabel} missing ${field}`);
}
assertString(check.id, `${checkLabel}.id`);
assertStatus(check.status, `${checkLabel}.status`);
assertString(check.summary, `${checkLabel}.summary`);
assertStringArray(check.evidence, `${checkLabel}.evidence`, { minLength: 1 });
if (Object.hasOwn(check, "blockerClass")) {
assert.ok(blockerTypes.has(check.blockerClass), `${checkLabel}.blockerClass must be known`);
}
}
if (Object.hasOwn(report, "localRuntimeObservations")) {
assertArray(report.localRuntimeObservations, `${label}.localRuntimeObservations`);
for (const [index, observation] of report.localRuntimeObservations.entries()) {
const observationLabel = `${label}.localRuntimeObservations[${index}]`;
assertObject(observation, observationLabel);
for (const field of ["id", "status", "command", "summary"]) {
assert.ok(Object.hasOwn(observation, field), `${observationLabel} missing ${field}`);
assertString(observation[field], `${observationLabel}.${field}`);
}
}
}
assertObject(report.liveOperation, `${label}.liveOperation`);
assertStatus(report.liveOperation.status, `${label}.liveOperation.status`);
for (const field of ["operationId", "traceId", "auditId", "evidenceId", "summary"]) {
assertString(report.liveOperation[field], `${label}.liveOperation.${field}`);
}
assertArray(report.blockers, `${label}.blockers`);
assertUnique(
report.blockers.map((blocker) => `${blocker.type}::${blocker.scope}`),
`${label}.blockers`
);
for (const [index, blocker] of report.blockers.entries()) {
const blockerLabel = `${label}.blockers[${index}]`;
assertObject(blocker, blockerLabel);
for (const field of ["type", "scope", "status", "summary"]) {
assert.ok(Object.hasOwn(blocker, field), `${blockerLabel} missing ${field}`);
}
assert.ok(blockerTypes.has(blocker.type), `${blockerLabel}.type must be a known blocker type`);
assertString(blocker.scope, `${blockerLabel}.scope`);
assert.ok(blockerStates.has(blocker.status), `${blockerLabel}.status must be open, acknowledged, or closed`);
assertString(blocker.summary, `${blockerLabel}.summary`);
}
assertObject(report.summary, `${label}.summary`);
assertStatus(report.summary.status, `${label}.summary.status`);
assertTimestamp(report.summary.observedAt, `${label}.summary.observedAt`);
assertString(report.summary.result, `${label}.summary.result`);
}
async function main() {
const reportFiles = await collectReportFiles();
assert.ok(reportFiles.length >= 1, "expected at least one dev-gate report JSON file");