test: harden M3 trusted loop smoke path

This commit is contained in:
Code Queue Review
2026-05-22 18:29:55 +00:00
parent 71d4411f89
commit 0ff408696a
4 changed files with 134 additions and 34 deletions
+56 -33
View File
@@ -33,6 +33,14 @@ const requiredM3Connection = Object.freeze({
const requiredM3GatewayIds = Object.freeze(["gwsimu_1", "gwsimu_2"]);
const runnerK3sKubeconfigPath = "/etc/rancher/k3s/k3s.yaml";
const m3FailureClassifications = Object.freeze({
targetMissing: "target_missing",
identityNotDistinct: "identity_not_distinct",
patchPanelWiringMissing: "patch_panel_wiring_missing",
operationFailed: "operation_failed",
evidenceMissing: "evidence_missing"
});
const serviceTargets = Object.freeze([
{
id: "box-simu-1",
@@ -90,11 +98,31 @@ function parseArgs(argv) {
return {
flags,
values,
dryRun: flags.has("--dry-run") || flags.has("--plan"),
live: flags.has("--live") || flags.has("--allow-live")
dryRun: flags.has("--dry-run") || flags.has("--plan") || flags.has("--source-read-only"),
live: flags.has("--live")
};
}
function classifyDirectTargetBlockerScope(scope) {
if (scope === "m3-patch-panel-wiring") {
return m3FailureClassifications.patchPanelWiringMissing;
}
if (scope === "m3-box-simu-identity" || scope === "m3-gateway-simu-identity") {
return m3FailureClassifications.identityNotDistinct;
}
if (scope === "m3-service-discovery" || scope === "m3-direct-target-missing") {
return m3FailureClassifications.targetMissing;
}
return m3FailureClassifications.operationFailed;
}
function classifyLiveOperationError(error) {
const message = error instanceof Error ? error.message : String(error);
return /\b(auditId|evidenceId)\b/u.test(message)
? m3FailureClassifications.evidenceMissing
: m3FailureClassifications.operationFailed;
}
function requireSafetyGates({ flags, dryRun, live }) {
const forbiddenFlags = [
"--prod",
@@ -112,28 +140,20 @@ function requireSafetyGates({ flags, dryRun, live }) {
assert.ok(!flags.has(flag), `${flag} is forbidden for DEV M3 smoke`);
}
assert.ok(
!(dryRun && live),
"DEV M3 smoke refuses to combine --dry-run/--plan with --live/--allow-live; plan mode never runs the M3 write path"
);
const confirmedLive =
live &&
flags.has("--confirm-dev") &&
flags.has("--confirmed-non-production") &&
!dryRun &&
!flags.has("--allow-live");
if (dryRun) {
if (!confirmedLive) {
return {
mode: "dry-run",
liveWriteAllowed: false
};
}
assert.ok(
live,
"DEV M3 smoke refuses to run by default; use --dry-run for a no-write plan or --live --confirm-dev --expect-non-prod after approval"
);
assert.ok(flags.has("--confirm-dev"), "live DEV smoke requires --confirm-dev");
assert.ok(
flags.has("--expect-non-prod") || flags.has("--confirmed-non-production"),
"live DEV smoke requires --expect-non-prod (legacy --confirmed-non-production is accepted only as compatibility)"
);
return {
mode: "live",
liveWriteAllowed: true
@@ -648,10 +668,10 @@ function createLiveOperationPlan() {
"targetPort=DI1",
"targetState.ports.DI1.propagatedBy=hwlab-patch-panel"
],
failureClassifications: Object.values(m3FailureClassifications),
refusalPolicy: [
"no arguments defaults to refusal",
"--dry-run/--plan cannot be combined with --live/--allow-live",
"live mode refuses without --confirm-dev and --expect-non-prod",
"commands missing any of --live --confirm-dev --confirmed-non-production stay source/read-only",
"a dry-run, source, local, fixture, or read-only blocker report leaves liveOperation.status as not_run or blocked, never pass"
]
};
@@ -963,7 +983,7 @@ async function resolveDirectM3Targets({ environmentTargets, kubernetesDiscovery
type: "observability_blocker",
scope: "m3-service-discovery",
status: "open",
classification: "runner_kubeconfig_readonly_gap",
classification: classifyDirectTargetBlockerScope("m3-service-discovery"),
sourceIssue: "pikasTech/HWLAB#46",
summary: `DEV ingress is reachable on frozen :16667, but direct M3 target discovery via ${kubernetesDiscovery.source} is ${kubernetesDiscovery.status}; environment URLs missing ${environmentTargets.missing.join(", ")}; discovery detail=${kubernetesDiscovery.summary}.`
});
@@ -972,7 +992,7 @@ async function resolveDirectM3Targets({ environmentTargets, kubernetesDiscovery
type: "observability_blocker",
scope: "m3-direct-target-missing",
status: "open",
classification: "direct_target_missing",
classification: classifyDirectTargetBlockerScope("m3-direct-target-missing"),
sourceIssue: "pikasTech/HWLAB#64",
summary: `Read-only service discovery did not expose enough callable DEV M3 direct targets: box-simu=${boxCandidates.length}/2, gateway-simu=${gatewayCandidates.length}/2, patch-panel=${patchCandidates.length}/1.`
});
@@ -983,7 +1003,7 @@ async function resolveDirectM3Targets({ environmentTargets, kubernetesDiscovery
type: "runtime_blocker",
scope: "m3-box-simu-identity",
status: "open",
classification: "direct_target_identity_gap",
classification: classifyDirectTargetBlockerScope("m3-box-simu-identity"),
sourceIssue: "pikasTech/HWLAB#64",
summary: `DEV exposes two box-simu endpoints, but live identities are not the required distinct resources res_boxsimu_1 and res_boxsimu_2; observed resources=${[...distinctBoxResources].join(", ") || "none"}.`
});
@@ -994,7 +1014,7 @@ async function resolveDirectM3Targets({ environmentTargets, kubernetesDiscovery
type: "runtime_blocker",
scope: "m3-gateway-simu-identity",
status: "open",
classification: "direct_target_identity_gap",
classification: classifyDirectTargetBlockerScope("m3-gateway-simu-identity"),
sourceIssue: "pikasTech/HWLAB#64",
summary: `DEV exposes two gateway-simu endpoints, but live gateway identities are not distinct; observed identities=${[...distinctGatewayIdentities].join(", ") || "none"}.`
});
@@ -1007,7 +1027,7 @@ async function resolveDirectM3Targets({ environmentTargets, kubernetesDiscovery
type: "runtime_blocker",
scope: "m3-patch-panel-wiring",
status: "open",
classification: "direct_target_m3_wiring_missing",
classification: classifyDirectTargetBlockerScope("m3-patch-panel-wiring"),
sourceIssue: "pikasTech/HWLAB#64",
summary: `DEV patch-panel is callable, but live wiring does not contain ${requiredM3Connection.fromResourceId}:${requiredM3Connection.fromPort} -> ${requiredM3Connection.toResourceId}:${requiredM3Connection.toPort}; active=${observedActive}; configured=${observedConfigured}.`
});
@@ -1256,8 +1276,9 @@ async function runLiveM3Targets(targets) {
assert.ok(after.ok, "box-simu-2 DI1 read failed");
assert.equal(after.json?.ports?.DI1?.value, true, "box-simu-2 DI1 must read true");
const auditId = route.json?.auditId ?? write.json?.auditId ?? after.json?.auditId;
const evidenceId = route.json?.evidenceId ?? write.json?.evidenceId ?? after.json?.evidenceId;
const deliveryResponse = route.json?.deliveries?.[0]?.response;
const auditId = route.json?.auditId ?? deliveryResponse?.auditId ?? write.json?.auditId ?? after.json?.auditId;
const evidenceId = route.json?.evidenceId ?? deliveryResponse?.evidenceId ?? write.json?.evidenceId ?? after.json?.evidenceId;
assert.equal(typeof auditId, "string", "live M3 result must include auditId");
assert.equal(typeof evidenceId, "string", "live M3 result must include evidenceId");
@@ -1319,6 +1340,7 @@ function baseReport({ commitId, observedAt }) {
confirmDevRequired: true,
expectNonProdRequired: true,
confirmedNonProductionRequired: true,
defaultSourceReadOnly: true,
dryRunPlanSupported: true,
dryRunCallsLiveEndpoints: false,
prodForbidden: true,
@@ -1447,12 +1469,12 @@ async function runDryRunPlan({ values }) {
type: "safety_blocker",
scope: "m3-live-write-authorization",
status: "open",
classification: "dry_run_plan_only",
classification: m3FailureClassifications.targetMissing,
summary: "Plan-only mode is intentionally non-mutating; run the bounded live smoke only after explicit DEV/non-PROD approval and after read-only preconditions identify the exact HWLAB targets."
});
report.summary = {
status: "blocked",
classification: "dry_run_plan_only",
classification: m3FailureClassifications.targetMissing,
observedAt,
result: "DRY-RUN plan emitted live write prerequisites, endpoint plan, and required evidence fields; it did not call DEV endpoints or produce DEV-LIVE evidence."
};
@@ -1537,12 +1559,12 @@ async function main() {
type: "network_blocker",
scope: "frp",
status: "open",
classification: "frp",
classification: m3FailureClassifications.targetMissing,
summary: "Blocked at the #33 DEV runtime readiness condition: public DEV ingress does not accept HWLAB health requests, so #36/M3 hardware-loop checks were not reached."
});
report.summary = {
status: "blocked",
classification: "frp",
classification: m3FailureClassifications.targetMissing,
observedAt,
result: "No live DEV M3 evidence was produced; fixture-backed local evidence was not used as a substitute."
};
@@ -1602,7 +1624,7 @@ async function main() {
report.blockers.push(...directTargets.blockers);
report.summary = {
status: "blocked",
classification: directTargets.blockers[0]?.classification ?? "direct_target_blocked",
classification: directTargets.blockers[0]?.classification ?? m3FailureClassifications.targetMissing,
observedAt,
result: "DEV ingress and direct targets were reachable, but M3 DEV-LIVE was not triggered because required identities or patch-panel wiring were missing."
};
@@ -1672,17 +1694,18 @@ async function main() {
`[dev-m3-smoke] operationId=${live.operationId} auditId=${live.auditId} evidenceId=${live.evidenceId}`
);
} catch (error) {
const classification = classifyLiveOperationError(error);
addNotRunM3Checks(report, "A live DEV M3 runtime contract check failed before all checks passed.");
report.blockers.push({
type: "runtime_blocker",
scope: "m3-hardware-loop-runtime",
status: "open",
classification: "hardware loop",
classification,
summary: error instanceof Error ? error.message : String(error)
});
report.summary = {
status: "blocked",
classification: "hardware loop",
classification,
observedAt,
result: "Live DEV M3 runtime was reachable but did not satisfy the M3 hardware-loop contract."
};