test: add M3 IO control E2E harness

This commit is contained in:
Code Queue Review
2026-05-23 05:01:05 +00:00
parent 7f030e0c4e
commit 59a47408ca
4 changed files with 1203 additions and 1 deletions
+2 -1
View File
@@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"validate": "node scripts/validate-contract.mjs && node scripts/deploy-contract-plan.mjs --check && node scripts/deploy-desired-state-plan.mjs --check && node scripts/dev-runtime-migration.mjs --check && node --test scripts/artifact-runtime-readiness-guard.test.mjs",
"check": "node --check internal/protocol/index.mjs && node --check internal/agent/index.mjs && node --check internal/agent/runtime.mjs && node --check internal/audit/index.mjs && node --check internal/db/runtime-store.mjs && node --check internal/db/runtime-store.test.mjs && node --check internal/mvp-gate/summary.mjs && node --check internal/cloud/db-contract.mjs && node --check internal/cloud/code-agent-contract.mjs && node --check internal/cloud/json-rpc.mjs && node --check internal/cloud/code-agent-chat.mjs && node --check internal/cloud/m3-io-control.mjs && node --check internal/cloud/server.mjs && node --check internal/sim/model.mjs && node --check internal/sim/model.test.mjs && node --check internal/sim/http.mjs && node --check internal/sim/l2-runtime.mjs && node --check internal/patchpanel/model.mjs && node --check internal/patchpanel/runtime.mjs && node --check cmd/hwlab-cloud-api/main.mjs && node --check cmd/hwlab-agent-mgr/main.mjs && node --check cmd/hwlab-agent-worker/main.mjs && node --check cmd/hwlab-box-simu/main.mjs && node --check cmd/hwlab-gateway-simu/main.mjs && node --check scripts/cloud-api-runtime-smoke.mjs && node --check scripts/code-agent-chat-smoke.mjs && node --check scripts/dev-edge-health-smoke.mjs && node --check scripts/src/dev-edge-health-smoke-lib.mjs && node --check scripts/validate-contract.mjs && node --check scripts/deploy-desired-state-plan.mjs && node --check scripts/src/deploy-desired-state-plan.mjs && node --check scripts/artifact-runtime-readiness-guard.mjs && node --check scripts/src/artifact-runtime-readiness-guard.mjs && node --check scripts/artifact-runtime-readiness-guard.test.mjs && node --check scripts/report-lifecycle.mjs && node --check scripts/report-lifecycle.test.mjs && node --check scripts/validate-dev-gate-report.mjs && node --check scripts/dev-m3-hardware-loop-smoke.test.mjs && node --check scripts/dev-cloud-workbench-smoke.test.mjs && node --check scripts/validate-dev-m3-cardinality.mjs && node --check scripts/validate-artifact-catalog.mjs && node --check scripts/refresh-artifact-catalog.mjs && node --check scripts/dev-artifact-publish.mjs && node --check scripts/src/dev-artifact-services.mjs && node --check scripts/src/registry-capabilities.mjs && node --check scripts/preflight-dev-base-image.mjs && node --check scripts/src/dev-base-image-preflight.mjs && node --check scripts/dev-evidence-blocker-aggregator.mjs && node --check scripts/src/dev-evidence-blocker-aggregator.mjs && node --check scripts/d601-k3s-readonly-observability.mjs && node --check scripts/src/d601-k3s-readonly-observability.mjs && node --check scripts/dev-runtime-migration.mjs && node --check scripts/src/dev-runtime-migration.mjs && node --check scripts/src/dev-runtime-migration.test.mjs && node --check scripts/l2-runtime-contract-smoke.mjs && node --check scripts/patch-panel-runtime-smoke.mjs && node --check scripts/export-web-gate-summary.mjs && node --check scripts/l6-cli-web-smoke.mjs && node --check tools/hwlab-cli/bin/hwlab-cli.mjs && node --check tools/hwlab-cli/lib/cli.mjs && node --check web/hwlab-cloud-web/app.mjs && node --check web/hwlab-cloud-web/gate-summary.mjs && node --check web/hwlab-cloud-web/scripts/check.mjs && node --check web/hwlab-cloud-web/scripts/build.mjs && node --check web/hwlab-cloud-web/scripts/dist-contract.mjs && node scripts/validate-contract.mjs && node scripts/validate-dev-gate-report.mjs && node scripts/report-lifecycle.test.mjs && node scripts/validate-dev-m3-cardinality.mjs && node scripts/validate-artifact-catalog.mjs && node scripts/deploy-desired-state-plan.mjs --check && node scripts/dev-runtime-migration.mjs --check && node scripts/dev-evidence-blocker-aggregator.mjs --check && node scripts/l2-runtime-contract-smoke.mjs && node scripts/l6-cli-web-smoke.mjs && node web/hwlab-cloud-web/scripts/check.mjs && node scripts/code-agent-chat-smoke.mjs && node --test scripts/artifact-runtime-readiness-guard.test.mjs scripts/dev-m3-hardware-loop-smoke.test.mjs scripts/dev-cloud-workbench-smoke.test.mjs scripts/deploy-desired-state-plan.test.mjs scripts/src/dev-deploy-apply.test.mjs scripts/src/dev-runtime-migration.test.mjs internal/agent/index.test.mjs internal/audit/index.test.mjs internal/db/schema.test.mjs internal/db/runtime-store.test.mjs internal/cloud/json-rpc.test.mjs internal/cloud/m3-io-control.test.mjs internal/cloud/server.test.mjs internal/sim/model.test.mjs internal/patchpanel/model.test.mjs internal/patchpanel/runtime.test.mjs && node scripts/cloud-api-runtime-smoke.mjs && sh -n scripts/bootstrap-skills.sh scripts/worker-entrypoint.sh",
"check": "node --check internal/protocol/index.mjs && node --check internal/agent/index.mjs && node --check internal/agent/runtime.mjs && node --check internal/audit/index.mjs && node --check internal/db/runtime-store.mjs && node --check internal/db/runtime-store.test.mjs && node --check internal/mvp-gate/summary.mjs && node --check internal/cloud/db-contract.mjs && node --check internal/cloud/code-agent-contract.mjs && node --check internal/cloud/json-rpc.mjs && node --check internal/cloud/code-agent-chat.mjs && node --check internal/cloud/m3-io-control.mjs && node --check internal/cloud/server.mjs && node --check internal/sim/model.mjs && node --check internal/sim/model.test.mjs && node --check internal/sim/http.mjs && node --check internal/sim/l2-runtime.mjs && node --check internal/patchpanel/model.mjs && node --check internal/patchpanel/runtime.mjs && node --check cmd/hwlab-cloud-api/main.mjs && node --check cmd/hwlab-agent-mgr/main.mjs && node --check cmd/hwlab-agent-worker/main.mjs && node --check cmd/hwlab-box-simu/main.mjs && node --check cmd/hwlab-gateway-simu/main.mjs && node --check scripts/cloud-api-runtime-smoke.mjs && node --check scripts/code-agent-chat-smoke.mjs && node --check scripts/dev-edge-health-smoke.mjs && node --check scripts/src/dev-edge-health-smoke-lib.mjs && node --check scripts/validate-contract.mjs && node --check scripts/deploy-desired-state-plan.mjs && node --check scripts/src/deploy-desired-state-plan.mjs && node --check scripts/artifact-runtime-readiness-guard.mjs && node --check scripts/src/artifact-runtime-readiness-guard.mjs && node --check scripts/artifact-runtime-readiness-guard.test.mjs && node --check scripts/report-lifecycle.mjs && node --check scripts/report-lifecycle.test.mjs && node --check scripts/validate-dev-gate-report.mjs && node --check scripts/dev-m3-hardware-loop-smoke.test.mjs && node --check scripts/m3-io-control-e2e.mjs && node --check scripts/src/m3-io-control-e2e.mjs && node --check scripts/m3-io-control-e2e.test.mjs && node --check scripts/dev-cloud-workbench-smoke.test.mjs && node --check scripts/validate-dev-m3-cardinality.mjs && node --check scripts/validate-artifact-catalog.mjs && node --check scripts/refresh-artifact-catalog.mjs && node --check scripts/dev-artifact-publish.mjs && node --check scripts/src/dev-artifact-services.mjs && node --check scripts/src/registry-capabilities.mjs && node --check scripts/preflight-dev-base-image.mjs && node --check scripts/src/dev-base-image-preflight.mjs && node --check scripts/dev-evidence-blocker-aggregator.mjs && node --check scripts/src/dev-evidence-blocker-aggregator.mjs && node --check scripts/d601-k3s-readonly-observability.mjs && node --check scripts/src/d601-k3s-readonly-observability.mjs && node --check scripts/dev-runtime-migration.mjs && node --check scripts/src/dev-runtime-migration.mjs && node --check scripts/src/dev-runtime-migration.test.mjs && node --check scripts/l2-runtime-contract-smoke.mjs && node --check scripts/patch-panel-runtime-smoke.mjs && node --check scripts/export-web-gate-summary.mjs && node --check scripts/l6-cli-web-smoke.mjs && node --check tools/hwlab-cli/bin/hwlab-cli.mjs && node --check tools/hwlab-cli/lib/cli.mjs && node --check web/hwlab-cloud-web/app.mjs && node --check web/hwlab-cloud-web/gate-summary.mjs && node --check web/hwlab-cloud-web/scripts/check.mjs && node --check web/hwlab-cloud-web/scripts/build.mjs && node --check web/hwlab-cloud-web/scripts/dist-contract.mjs && node scripts/validate-contract.mjs && node scripts/validate-dev-gate-report.mjs && node scripts/report-lifecycle.test.mjs && node scripts/validate-dev-m3-cardinality.mjs && node scripts/validate-artifact-catalog.mjs && node scripts/deploy-desired-state-plan.mjs --check && node scripts/dev-runtime-migration.mjs --check && node scripts/dev-evidence-blocker-aggregator.mjs --check && node scripts/l2-runtime-contract-smoke.mjs && node scripts/l6-cli-web-smoke.mjs && node web/hwlab-cloud-web/scripts/check.mjs && node scripts/code-agent-chat-smoke.mjs && node --test scripts/artifact-runtime-readiness-guard.test.mjs scripts/dev-m3-hardware-loop-smoke.test.mjs scripts/m3-io-control-e2e.test.mjs scripts/dev-cloud-workbench-smoke.test.mjs scripts/deploy-desired-state-plan.test.mjs scripts/src/dev-deploy-apply.test.mjs scripts/src/dev-runtime-migration.test.mjs internal/agent/index.test.mjs internal/audit/index.test.mjs internal/db/schema.test.mjs internal/db/runtime-store.test.mjs internal/cloud/json-rpc.test.mjs internal/cloud/m3-io-control.test.mjs internal/cloud/server.test.mjs internal/sim/model.test.mjs internal/patchpanel/model.test.mjs internal/patchpanel/runtime.test.mjs && node scripts/cloud-api-runtime-smoke.mjs && sh -n scripts/bootstrap-skills.sh scripts/worker-entrypoint.sh",
"dev-base-image:preflight": "node scripts/preflight-dev-base-image.mjs",
"cloud-api:smoke": "node scripts/cloud-api-runtime-smoke.mjs",
"m1:smoke": "node scripts/m1-contract-smoke.mjs",
@@ -30,6 +30,7 @@
"dev-artifact:preflight": "node scripts/dev-artifact-publish.mjs --preflight",
"dev-artifact:publish": "node scripts/dev-artifact-publish.mjs --publish",
"docs:validate:m3-rollout": "node scripts/validate-m3-rollout-runbook.mjs",
"m3:io:e2e": "node scripts/m3-io-control-e2e.mjs",
"d601:k3s:readonly": "node scripts/d601-k3s-readonly-observability.mjs",
"runner:issue-visibility:preflight": "node scripts/runner-issue-visibility-preflight.mjs",
"report:lifecycle:test": "node scripts/report-lifecycle.test.mjs",
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env node
import {
formatM3IoControlE2eFailure,
runM3IoControlE2eCli
} from "./src/m3-io-control-e2e.mjs";
try {
process.exitCode = await runM3IoControlE2eCli(process.argv.slice(2));
} catch (error) {
process.stdout.write(`${JSON.stringify(formatM3IoControlE2eFailure(error), null, 2)}\n`);
process.exitCode = 1;
}
+272
View File
@@ -0,0 +1,272 @@
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import test from "node:test";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
checkFrontendNoDirectRuntimeCalls,
classifyM3IoControlReport,
controlPathReachablePersistenceBlocked,
expectedM3IoLiveSequence,
parseM3IoControlE2eArgs,
requireM3IoControlE2eGates,
resolveM3IoEndpoint,
validateExpectedRequestSchemas,
validateM3IoLiveResponse
} from "./src/m3-io-control-e2e.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
test("M3 IO E2E default mode is SOURCE/static and never permits mutation", () => {
const args = parseM3IoControlE2eArgs([]);
assert.equal(args.live, false);
assert.equal(args.target, "frontend");
assert.equal(args.frontendUrl, "http://74.48.78.17:16666/");
assert.deepEqual(requireM3IoControlE2eGates(args), {
mode: "source-static",
liveWriteAllowed: false,
mutationAllowed: false
});
});
test("M3 IO E2E live mode requires explicit DEV and non-production confirmations", () => {
assert.throws(
() => requireM3IoControlE2eGates(parseM3IoControlE2eArgs(["--live", "--confirm-dev"])),
/requires --live --confirm-dev --confirmed-non-production/u
);
assert.throws(
() => requireM3IoControlE2eGates(parseM3IoControlE2eArgs(["--live", "--confirm-dev", "--confirmed-non-production", "--dry-run"])),
/cannot be combined/u
);
assert.deepEqual(
requireM3IoControlE2eGates(parseM3IoControlE2eArgs(["--live", "--confirm-dev", "--confirmed-non-production"])),
{
mode: "dev-live",
liveWriteAllowed: true,
mutationAllowed: true
}
);
assert.deepEqual(
requireM3IoControlE2eGates(parseM3IoControlE2eArgs(["--live", "--confirm-dev", "--expect-non-prod"])),
{
mode: "dev-live",
liveWriteAllowed: true,
mutationAllowed: true
}
);
});
test("M3 IO E2E rejects dangerous flags and non-DEV live endpoints", () => {
assert.throws(
() => requireM3IoControlE2eGates(parseM3IoControlE2eArgs(["--prod"])),
/forbidden/u
);
assert.throws(
() => resolveM3IoEndpoint(parseM3IoControlE2eArgs([
"--live",
"--confirm-dev",
"--confirmed-non-production",
"--url",
"http://127.0.0.1:16666/"
])),
/live endpoint must match target/u
);
assert.equal(
resolveM3IoEndpoint(parseM3IoControlE2eArgs(["--live", "--confirm-dev", "--confirmed-non-production"])).href,
"http://74.48.78.17:16666/v1/m3/io"
);
assert.throws(
() => resolveM3IoEndpoint(parseM3IoControlE2eArgs([
"--live",
"--confirm-dev",
"--confirmed-non-production",
"--url",
"http://74.48.78.17:16667/"
])),
/frontend uses DEV/u
);
assert.equal(
resolveM3IoEndpoint(parseM3IoControlE2eArgs([
"--live",
"--confirm-dev",
"--confirmed-non-production",
"--target",
"api"
])).href,
"http://74.48.78.17:16667/v1/m3/io"
);
});
test("M3 IO E2E source mode CLI prints non-mutating SOURCE/DRY-RUN report", () => {
const stdout = execFileSync("node", ["scripts/m3-io-control-e2e.mjs"], {
cwd: repoRoot,
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"]
});
const report = JSON.parse(stdout);
assert.equal(report.mode, "source-static");
assert.equal(report.sourceKind, "SOURCE");
assert.equal(report.liveMutation.attempted, false);
assert.equal(report.liveMutation.allowed, false);
assert.equal(report.dryRunPlan.sourceKind, "DRY-RUN");
assert.equal(report.dryRunPlan.liveWriteWillRun, false);
assert.equal(report.summary.classification, "source_static_contract_checked");
assert.equal(report.summary.trustedGreen, false);
});
test("M3 IO E2E request schemas are fixed to DO1 write and DI1 read", () => {
const result = validateExpectedRequestSchemas(expectedM3IoLiveSequence());
assert.equal(result.status, "pass");
assert.deepEqual(
result.sequence.map((step) => [step.action, step.gatewayId, step.resourceId, step.boxId, step.port, step.value]),
[
["do.write", "gwsimu_1", "res_boxsimu_1", "boxsimu_1", "DO1", true],
["di.read", "gwsimu_2", "res_boxsimu_2", "boxsimu_2", "DI1", undefined],
["do.write", "gwsimu_1", "res_boxsimu_1", "boxsimu_1", "DO1", false],
["di.read", "gwsimu_2", "res_boxsimu_2", "boxsimu_2", "DI1", undefined]
]
);
const invalid = expectedM3IoLiveSequence().map((step) =>
step.id === "write-do1-true"
? { ...step, request: { ...step.request, port: "DI1" } }
: step
);
assert.equal(validateExpectedRequestSchemas(invalid).status, "fail");
});
test("M3 IO E2E classifies reachable control with blocked durable persistence correctly", () => {
const operations = expectedM3IoLiveSequence().map((step) => ({
id: step.id,
status: "succeeded",
schema: { status: "pass", issues: [] },
evidenceState: {
status: "blocked",
sourceKind: "BLOCKED",
durable: false
}
}));
const classification = classifyM3IoControlReport({
checks: [{ id: "source", status: "pass" }],
liveOperations: operations
});
assert.deepEqual(classification, {
status: "blocked",
classification: controlPathReachablePersistenceBlocked,
trustedGreen: false
});
});
test("M3 IO E2E classifies trusted green only with durable DEV-LIVE evidence", () => {
const operations = expectedM3IoLiveSequence().map((step) => ({
id: step.id,
status: "succeeded",
schema: { status: "pass", issues: [] },
evidenceState: {
status: "green",
sourceKind: "DEV-LIVE",
durable: true
}
}));
assert.deepEqual(
classifyM3IoControlReport({
checks: [{ id: "source", status: "pass" }],
liveOperations: operations
}),
{
status: "pass",
classification: "trusted_green",
trustedGreen: true
}
);
});
test("M3 IO E2E frontend guardrail catches direct gateway or patch-panel calls", () => {
const appSource = `
async function runM3IoAction() {
return fetchJson("/v1/m3/io", { method: "POST" });
}
async function other() {
return fetch("/v1");
}
`;
const pass = checkFrontendNoDirectRuntimeCalls({
appSource,
artifactPublisherSource: 'if (url.pathname === "/v1/m3/io") proxyCloudApi();'
});
assert.equal(pass.status, "pass");
const fail = checkFrontendNoDirectRuntimeCalls({
appSource: `
async function runM3IoAction() {
await fetch("http://hwlab-gateway-simu-1.hwlab-dev.svc.cluster.local:7101/invoke");
}
`,
artifactPublisherSource: ""
});
assert.equal(fail.status, "fail");
assert.ok(fail.issues.some((issue) => /direct runtime|same-origin|runM3IoAction/u.test(issue)));
});
test("M3 IO E2E live response schema requires identifiers and frontendBypass=false", () => {
const [step] = expectedM3IoLiveSequence();
const valid = validateM3IoLiveResponse({
status: "succeeded",
accepted: true,
action: step.action,
chain: {
projectId: "prj_m3_hardware_loop",
sourceGatewayId: "gwsimu_1",
sourceGatewaySessionId: "gws_gwsimu_1",
sourceResourceId: "res_boxsimu_1",
sourceBoxId: "boxsimu_1",
sourcePort: "DO1",
targetGatewayId: "gwsimu_2",
targetGatewaySessionId: "gws_gwsimu_2",
targetResourceId: "res_boxsimu_2",
targetBoxId: "boxsimu_2",
targetPort: "DI1",
patchPanelServiceId: "hwlab-patch-panel"
},
controlPath: {
cloudApi: true,
gatewaySimu: true,
boxSimu: true,
patchPanel: true,
frontendBypass: false
},
operationId: "op_m3_test",
traceId: "trc_m3_test",
auditId: "aud_m3_test",
evidenceId: "evd_m3_test",
evidenceState: {
status: "blocked",
sourceKind: "BLOCKED"
},
result: {
value: true
}
}, step);
assert.equal(valid.status, "pass");
const invalid = validateM3IoLiveResponse({
status: "succeeded",
accepted: true,
action: step.action,
chain: {},
controlPath: {
frontendBypass: true
},
traceId: "trc_m3_test",
evidenceState: {
status: "blocked"
},
result: {
value: false
}
}, step);
assert.equal(invalid.status, "fail");
assert.ok(invalid.issues.some((issue) => /operationId|frontendBypass|chain|result\.value/u.test(issue)));
});
+917
View File
@@ -0,0 +1,917 @@
import { randomUUID } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
M3_IO_CHAIN,
M3_IO_CONTROL_CONTRACT_VERSION,
M3_IO_CONTROL_ROUTE,
describeM3IoControl
} from "../../internal/cloud/m3-io-control.mjs";
export const M3_IO_E2E_CONTRACT_VERSION = "m3-io-control-e2e-v1";
export const defaultFrontendUrl = "http://74.48.78.17:16666/";
export const defaultApiUrl = "http://74.48.78.17:16667/v1/m3/io";
export const controlPathReachablePersistenceBlocked = "control_path_reachable_trusted_persistence_blocked";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const labels = Object.freeze({
source: "SOURCE",
local: "LOCAL",
dryRun: "DRY-RUN",
devLive: "DEV-LIVE",
blocked: "BLOCKED"
});
const forbiddenLiveFlags = Object.freeze([
"--prod",
"--production",
"--real-hardware",
"--read-secret",
"--read-token",
"--deploy",
"--apply",
"--restart",
"--restart-runtime",
"--restart-unidesk",
"--restart-code-queue",
"--restart-backend-core"
]);
const valueFlags = new Set(["--report", "--output", "--url", "--api-url", "--target", "--timeout-ms"]);
const booleanFlags = new Set([
"--live",
"--confirm-dev",
"--confirmed-non-production",
"--expect-non-prod",
"--source",
"--source-static",
"--dry-run",
"--pretty",
...forbiddenLiveFlags
]);
export function parseM3IoControlE2eArgs(argv = []) {
const flags = new Set();
const values = new Map();
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (!arg.startsWith("--")) {
throw new Error(`unexpected positional argument: ${arg}`);
}
if (valueFlags.has(arg)) {
const value = argv[index + 1];
if (!value || value.startsWith("--")) {
throw new Error(`${arg} requires a value`);
}
values.set(arg.slice(2), value);
index += 1;
continue;
}
if (!booleanFlags.has(arg)) {
throw new Error(`unknown flag: ${arg}`);
}
flags.add(arg);
}
const target = values.get("target") ?? (values.has("api-url") ? "api" : "frontend");
if (!["frontend", "api"].includes(target)) {
throw new Error("--target must be frontend or api");
}
const timeoutMs = parsePositiveInteger(values.get("timeout-ms"), 8000);
return {
flags,
values,
live: flags.has("--live"),
source: flags.has("--source") || flags.has("--source-static"),
dryRun: flags.has("--dry-run"),
confirmDev: flags.has("--confirm-dev"),
confirmedNonProduction: flags.has("--confirmed-non-production") || flags.has("--expect-non-prod"),
target,
frontendUrl: values.get("url") ?? defaultFrontendUrl,
apiUrl: values.get("api-url") ?? defaultApiUrl,
reportPath: values.get("report") ?? values.get("output") ?? null,
timeoutMs,
pretty: flags.has("--pretty")
};
}
export function requireM3IoControlE2eGates(args) {
for (const flag of forbiddenLiveFlags) {
if (args.flags.has(flag)) {
throw new Error(`${flag} is forbidden for the DEV M3 IO control harness`);
}
}
if (!args.live) {
return {
mode: "source-static",
liveWriteAllowed: false,
mutationAllowed: false
};
}
if (args.dryRun || args.source) {
throw new Error("--live cannot be combined with --dry-run, --source, or --source-static");
}
if (!args.confirmDev || !args.confirmedNonProduction) {
throw new Error("live mode requires --live --confirm-dev --confirmed-non-production");
}
return {
mode: "dev-live",
liveWriteAllowed: true,
mutationAllowed: true
};
}
export function expectedM3IoLiveSequence() {
return [
{
id: "write-do1-true",
method: "POST",
path: M3_IO_CONTROL_ROUTE,
action: "do.write",
expectedValue: true,
mutatesDevState: true,
request: doWriteRequest(true)
},
{
id: "read-di1-true",
method: "POST",
path: M3_IO_CONTROL_ROUTE,
action: "di.read",
expectedValue: true,
mutatesDevState: false,
request: diReadRequest()
},
{
id: "write-do1-false",
method: "POST",
path: M3_IO_CONTROL_ROUTE,
action: "do.write",
expectedValue: false,
mutatesDevState: true,
request: doWriteRequest(false)
},
{
id: "read-di1-false",
method: "POST",
path: M3_IO_CONTROL_ROUTE,
action: "di.read",
expectedValue: false,
mutatesDevState: false,
request: diReadRequest()
}
];
}
export async function buildM3IoControlSourceReport(options = {}) {
const root = options.repoRoot ?? repoRoot;
const checks = await runM3IoControlSourceChecks({ repoRoot: root });
const classification = checks.every((check) => check.status === "pass")
? "source_static_contract_checked"
: "source_contract_failed";
const status = classification === "source_static_contract_checked" ? "pass" : "blocked";
return {
tool: "m3-io-control-e2e",
contractVersion: M3_IO_E2E_CONTRACT_VERSION,
mode: "source-static",
sourceKind: labels.source,
evidenceLabels: evidenceLabelDescriptions(),
liveMutation: {
attempted: false,
allowed: false,
reason: "Default SOURCE/static mode never POSTs to DEV and never mutates simulator state."
},
endpoints: {
frontendSameOriginBase: defaultFrontendUrl,
frontendSameOriginPath: M3_IO_CONTROL_ROUTE,
cloudApiPath: M3_IO_CONTROL_ROUTE,
directCloudApiEndpoint: defaultApiUrl,
gatewayBoxPatchPanelDirectCallsAllowed: false
},
dryRunPlan: {
sourceKind: labels.dryRun,
liveWriteWillRun: false,
sequence: expectedM3IoLiveSequence().map((step) => ({
id: step.id,
method: step.method,
path: step.path,
action: step.action,
expectedValue: step.expectedValue,
mutatesDevState: step.mutatesDevState
}))
},
checks,
summary: {
status,
classification,
trustedGreen: false,
observedAt: isoNow(),
result: status === "pass"
? "SOURCE/static M3 IO control contract passed; DEV-LIVE was not run."
: "SOURCE/static M3 IO control contract failed; DEV-LIVE must not run until fixed."
}
};
}
export async function runM3IoControlSourceChecks({ repoRoot: root = repoRoot } = {}) {
const [serverSource, m3Source, appSource, htmlSource, artifactPublisherSource] = await Promise.all([
readRepoText(root, "internal/cloud/server.mjs"),
readRepoText(root, "internal/cloud/m3-io-control.mjs"),
readRepoText(root, "web/hwlab-cloud-web/app.mjs"),
readRepoText(root, "web/hwlab-cloud-web/index.html"),
readRepoText(root, "scripts/dev-artifact-publish.mjs")
]);
const contract = describeM3IoControl({
env: {
HWLAB_M3_IO_CONTROL_ENABLED: "true"
}
});
const frontendGuardrails = checkFrontendNoDirectRuntimeCalls({
appSource,
htmlSource,
artifactPublisherSource
});
const requestSchema = validateExpectedRequestSchemas(expectedM3IoLiveSequence());
const responseSchema = validateM3IoSourceResponseContract(m3Source);
return [
checkResult(
"route-name-contract",
contract.route === M3_IO_CONTROL_ROUTE &&
M3_IO_CONTROL_ROUTE === "/v1/m3/io" &&
contract.contractVersion === M3_IO_CONTROL_CONTRACT_VERSION,
"cloud-api route is /v1/m3/io with the m3-io-control-v1 contract.",
{
route: contract.route,
contractVersion: contract.contractVersion,
evidenceLevel: labels.source
}
),
checkResult(
"m3-chain-contract",
routeChainMatches(contract.chain),
"M3 chain is box-simu-1 DO1 -> hwlab-patch-panel -> box-simu-2 DI1.",
{
chain: contract.chain,
evidenceLevel: labels.source
}
),
checkResult(
"cloud-api-rest-route-contract",
serverSource.includes("url.pathname === M3_IO_CONTROL_ROUTE") &&
serverSource.includes("handleM3IoControlHttp") &&
serverSource.includes("m3IoControl: describeM3IoControl"),
"cloud-api exposes GET/POST /v1/m3/io and advertises it from /v1.",
{
route: M3_IO_CONTROL_ROUTE,
evidenceLevel: labels.source
}
),
checkResult(
"request-schema-contract",
requestSchema.status === "pass",
"Expected DEV live sequence uses only do.write on DO1 and di.read on DI1.",
{
issues: requestSchema.issues,
sequence: requestSchema.sequence,
evidenceLevel: labels.dryRun
}
),
checkResult(
"response-schema-contract",
responseSchema.status === "pass",
"M3 IO responses carry operationId, traceId, auditId, evidenceId, evidenceState, and frontendBypass=false.",
{
issues: responseSchema.issues,
evidenceLevel: labels.source
}
),
checkResult(
"frontend-same-origin-guardrail",
frontendGuardrails.status === "pass",
"Cloud Web only calls same-origin /v1/m3/io for the user control path and has no direct gateway/box/patch-panel fetches.",
{
fetchTargets: frontendGuardrails.fetchTargets,
issues: frontendGuardrails.issues,
evidenceLevel: labels.source
}
)
];
}
export function checkFrontendNoDirectRuntimeCalls({ appSource, htmlSource = "", artifactPublisherSource = "" }) {
const source = `${appSource}\n${htmlSource}`;
const fetchTargets = literalFetchTargets(appSource);
const issues = [];
const allowedSameOriginTargets = new Set([
"/help.md",
"/health/live",
"/v1",
"/v1/agent/chat",
"/v1/m3/io",
"/json-rpc"
]);
for (const target of fetchTargets) {
if (!target.startsWith("/")) {
issues.push(`frontend fetch target is not same-origin relative: ${target}`);
}
if (target.startsWith("/") && !allowedSameOriginTargets.has(target)) {
issues.push(`frontend fetch target is not in the allowed same-origin set: ${target}`);
}
}
const directRuntimePatterns = [
/\bfetch(?:Json)?\(\s*["'`][^"'`]*(?:gateway-simu|box-simu|patch-panel|:7101|:7201|:7301)/iu,
/\bfetch(?:Json)?\(\s*["'`]https?:\/\//iu,
/https?:\/\/[^"'`\s]*(?:gateway-simu|box-simu|patch-panel|:7101|:7201|:7301)/iu,
/(?:\/invoke|\/sync\/tick)/iu
];
for (const pattern of directRuntimePatterns) {
if (pattern.test(source)) {
issues.push(`frontend source matches forbidden direct runtime pattern: ${pattern.source}`);
}
}
const actionBody = functionBody(appSource, "runM3IoAction");
if (!/fetchJson\("\/v1\/m3\/io"/u.test(actionBody)) {
issues.push("runM3IoAction must POST through same-origin /v1/m3/io");
}
if (/fetchJson\(["'`](?!\/v1\/m3\/io)/u.test(actionBody) || /\bfetch\(/u.test(actionBody)) {
issues.push("runM3IoAction must not call any path other than fetchJson(\"/v1/m3/io\")");
}
if (!/url\.pathname === "\/v1\/m3\/io"/u.test(artifactPublisherSource)) {
issues.push("cloud-web artifact server must proxy same-origin /v1/m3/io to cloud-api");
}
return {
status: issues.length === 0 ? "pass" : "fail",
fetchTargets,
issues
};
}
export function validateExpectedRequestSchemas(sequence) {
const issues = [];
for (const step of sequence) {
if (step.path !== M3_IO_CONTROL_ROUTE || step.method !== "POST") {
issues.push(`${step.id} must POST ${M3_IO_CONTROL_ROUTE}`);
}
if (!["do.write", "di.read"].includes(step.request.action)) {
issues.push(`${step.id} action is invalid`);
}
if (step.request.action === "do.write") {
if (
step.request.gatewayId !== M3_IO_CHAIN.sourceGatewayId ||
step.request.resourceId !== M3_IO_CHAIN.sourceResourceId ||
step.request.boxId !== M3_IO_CHAIN.sourceBoxId ||
step.request.port !== M3_IO_CHAIN.sourcePort ||
typeof step.request.value !== "boolean"
) {
issues.push(`${step.id} must write ${M3_IO_CHAIN.sourceResourceId}:${M3_IO_CHAIN.sourcePort} with a boolean value`);
}
}
if (step.request.action === "di.read") {
if (
step.request.gatewayId !== M3_IO_CHAIN.targetGatewayId ||
step.request.resourceId !== M3_IO_CHAIN.targetResourceId ||
step.request.boxId !== M3_IO_CHAIN.targetBoxId ||
step.request.port !== M3_IO_CHAIN.targetPort ||
Object.hasOwn(step.request, "value")
) {
issues.push(`${step.id} must read ${M3_IO_CHAIN.targetResourceId}:${M3_IO_CHAIN.targetPort} without a value`);
}
}
}
return {
status: issues.length === 0 ? "pass" : "fail",
issues,
sequence: sequence.map((step) => ({
id: step.id,
action: step.request.action,
gatewayId: step.request.gatewayId,
resourceId: step.request.resourceId,
boxId: step.request.boxId,
port: step.request.port,
value: Object.hasOwn(step.request, "value") ? step.request.value : undefined
}))
};
}
export function validateM3IoSourceResponseContract(m3Source) {
const requiredPatterns = [
/operationId:\s*input\.operation\.operationId/u,
/traceId:\s*input\.meta\.traceId/u,
/auditId:\s*records\.auditEvent\.auditId/u,
/evidenceId:\s*records\.evidenceRecord\.evidenceId/u,
/evidenceState:\s*evidenceState\(/u,
/persistence:\s*persistenceSummary\(/u,
/frontendBypass:\s*false/u,
/patchPanelOnlyPropagation:\s*true/u,
/sourceKind:\s*"DEV-LIVE"/u,
/sourceKind:\s*"BLOCKED"/u
];
const issues = requiredPatterns
.filter((pattern) => !pattern.test(m3Source))
.map((pattern) => `missing response contract pattern: ${pattern.source}`);
return {
status: issues.length === 0 ? "pass" : "fail",
issues
};
}
export function validateM3IoLiveResponse(payload, step) {
const issues = [];
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
return {
status: "fail",
issues: ["response body must be a JSON object"]
};
}
for (const field of ["status", "accepted", "action", "chain", "controlPath", "traceId", "evidenceState"]) {
if (!Object.hasOwn(payload, field)) {
issues.push(`missing ${field}`);
}
}
for (const field of ["operationId", "auditId", "evidenceId"]) {
if (payload.status === "succeeded" && !stringValue(payload[field])) {
issues.push(`missing ${field} on succeeded response`);
}
}
if (payload.action !== step.action) {
issues.push(`expected action ${step.action}, got ${payload.action}`);
}
if (payload.controlPath?.frontendBypass !== false) {
issues.push("controlPath.frontendBypass must be false");
}
if (payload.controlPath?.cloudApi !== true || payload.controlPath?.gatewaySimu !== true || payload.controlPath?.boxSimu !== true) {
issues.push("controlPath must prove cloudApi, gatewaySimu, and boxSimu were used");
}
if (step.action === "do.write" && payload.controlPath?.patchPanel !== true) {
issues.push("do.write must prove patchPanel=true");
}
if (payload.chain && !routeChainMatches(payload.chain)) {
issues.push("response chain does not match the required M3 DO1->DI1 route");
}
if (payload.status === "succeeded") {
const actualValue = payload.result?.value;
if (actualValue !== step.expectedValue) {
issues.push(`expected result.value ${String(step.expectedValue)}, got ${String(actualValue)}`);
}
}
return {
status: issues.length === 0 ? "pass" : "fail",
issues
};
}
export function classifyM3IoControlReport(report) {
const sourceChecks = report.checks ?? [];
if (sourceChecks.some((check) => check.status !== "pass")) {
return {
status: "blocked",
classification: "source_contract_failed",
trustedGreen: false
};
}
const operations = report.liveOperations ?? [];
if (operations.length === 0) {
return {
status: "pass",
classification: "source_static_contract_checked",
trustedGreen: false
};
}
if (operations.some((operation) => operation.status === "unreachable" || operation.status === "http_error")) {
return {
status: "blocked",
classification: "control_path_unreachable",
trustedGreen: false
};
}
if (operations.some((operation) => operation.status !== "succeeded" || operation.schema?.status === "fail")) {
return {
status: "blocked",
classification: "control_path_blocked",
trustedGreen: false
};
}
const allTrusted = operations.every((operation) =>
operation.evidenceState?.status === "green" &&
operation.evidenceState?.sourceKind === labels.devLive &&
operation.evidenceState?.durable === true
);
if (!allTrusted) {
return {
status: "blocked",
classification: controlPathReachablePersistenceBlocked,
trustedGreen: false
};
}
return {
status: "pass",
classification: "trusted_green",
trustedGreen: true
};
}
export async function runM3IoControlLiveReport(args, sourceReport) {
const endpoint = resolveM3IoEndpoint(args);
const contractProbe = await requestJson(endpoint.href, {
method: "GET",
timeoutMs: args.timeoutMs
});
const operations = [];
const sourceChecks = sourceReport.checks ?? [];
if (!contractProbe.ok) {
operations.push({
id: "contract-probe",
status: "unreachable",
sourceKind: labels.devLive,
endpoint: endpoint.href,
httpStatus: contractProbe.status,
error: contractProbe.error
});
} else if (contractProbe.body?.route !== M3_IO_CONTROL_ROUTE || !routeChainMatches(contractProbe.body?.chain)) {
operations.push({
id: "contract-probe",
status: "blocked",
sourceKind: labels.devLive,
endpoint: endpoint.href,
httpStatus: contractProbe.status,
error: "live /v1/m3/io contract did not match the source M3 route"
});
} else {
for (const step of expectedM3IoLiveSequence()) {
const operation = await postM3IoLiveStep(endpoint.href, step, args.timeoutMs);
operations.push(operation);
if (operation.status !== "succeeded") {
break;
}
}
}
const report = {
...sourceReport,
mode: "dev-live",
sourceKind: labels.devLive,
liveMutation: {
attempted: true,
allowed: true,
reason: "DEV live mode was explicitly gated by --live --confirm-dev --confirmed-non-production."
},
endpoints: {
...sourceReport.endpoints,
selectedTarget: args.target,
selectedEndpoint: endpoint.href,
selectedEndpointKind: endpoint.kind
},
contractProbe: {
ok: contractProbe.ok,
status: contractProbe.status,
route: contractProbe.body?.route ?? null,
chain: contractProbe.body?.chain ?? null,
error: contractProbe.error ?? null
},
liveOperations: operations,
summary: {
...sourceReport.summary,
observedAt: isoNow()
}
};
report.summary = {
...report.summary,
...classifyM3IoControlReport({ checks: sourceChecks, liveOperations: operations })
};
report.summary.result = resultTextForClassification(report.summary.classification);
return report;
}
export function resolveM3IoEndpoint(args) {
const endpoint = args.target === "api"
? new URL(args.apiUrl)
: new URL(M3_IO_CONTROL_ROUTE, args.frontendUrl);
const port = endpoint.port || (endpoint.protocol === "http:" ? "80" : "443");
const allowed =
endpoint.protocol === "http:" &&
endpoint.hostname === "74.48.78.17" &&
endpoint.pathname === M3_IO_CONTROL_ROUTE &&
((args.target === "frontend" && port === "16666") || (args.target === "api" && port === "16667"));
if (!allowed) {
throw new Error("live endpoint must match target: frontend uses DEV http://74.48.78.17:16666/v1/m3/io; api uses DEV http://74.48.78.17:16667/v1/m3/io");
}
return {
href: endpoint.href,
kind: port === "16666" ? "cloud-web-same-origin" : "cloud-api-direct",
port: Number(port)
};
}
export async function runM3IoControlE2eCli(argv = []) {
const args = parseM3IoControlE2eArgs(argv);
const gates = requireM3IoControlE2eGates(args);
const sourceReport = await buildM3IoControlSourceReport();
const report = gates.mode === "dev-live"
? await runM3IoControlLiveReport(args, sourceReport)
: sourceReport;
if (args.reportPath) {
await writeReport(args.reportPath, report);
}
process.stdout.write(`${JSON.stringify(report, null, args.pretty ? 2 : 0)}\n`);
return report.summary.status === "pass" ? 0 : 1;
}
export function formatM3IoControlE2eFailure(error) {
return {
tool: "m3-io-control-e2e",
contractVersion: M3_IO_E2E_CONTRACT_VERSION,
status: "error",
sourceKind: labels.blocked,
error: {
message: error instanceof Error ? error.message : String(error)
}
};
}
async function postM3IoLiveStep(endpoint, step, timeoutMs) {
const traceId = `trc_m3_io_e2e_${step.id.replaceAll("-", "_")}_${randomUUID()}`;
const requestId = `req_m3_io_e2e_${step.id.replaceAll("-", "_")}_${randomUUID()}`;
const body = {
...step.request,
projectId: M3_IO_CHAIN.projectId,
traceId,
requestId,
actorId: "usr_hwlab_m3_io_e2e"
};
const response = await requestJson(endpoint, {
method: "POST",
body,
timeoutMs,
headers: {
"x-trace-id": traceId,
"x-request-id": requestId,
"x-actor-id": "usr_hwlab_m3_io_e2e"
}
});
if (!response.ok) {
return {
id: step.id,
action: step.action,
status: response.status === 0 ? "unreachable" : "http_error",
sourceKind: labels.devLive,
endpoint,
httpStatus: response.status,
error: response.error,
request: redactLiveRequest(body)
};
}
const schema = validateM3IoLiveResponse(response.body, step);
return {
id: step.id,
action: step.action,
status: response.body?.status === "succeeded" && schema.status === "pass" ? "succeeded" : "blocked",
sourceKind: labels.devLive,
endpoint,
httpStatus: response.status,
request: redactLiveRequest(body),
schema,
operationId: response.body?.operationId ?? null,
traceId: response.body?.traceId ?? traceId,
auditId: response.body?.auditId ?? null,
evidenceId: response.body?.evidenceId ?? null,
evidenceState: response.body?.evidenceState ?? null,
accepted: response.body?.accepted ?? null,
resultValue: response.body?.result?.value,
blocker: response.body?.blocker ?? null,
controlPath: response.body?.controlPath ?? null
};
}
async function requestJson(url, { method = "GET", body, timeoutMs = 8000, headers = {} } = {}) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
method,
headers: body === undefined
? {
accept: "application/json",
...headers
}
: {
accept: "application/json",
"content-type": "application/json",
...headers
},
body: body === undefined ? undefined : JSON.stringify(body),
signal: controller.signal
});
const text = await response.text();
let parsed = null;
try {
parsed = text ? JSON.parse(text) : null;
} catch {
parsed = null;
}
return {
ok: response.ok,
status: response.status,
body: parsed,
error: response.ok ? null : parsed?.error?.message ?? parsed?.blocker?.message ?? response.statusText
};
} catch (error) {
return {
ok: false,
status: 0,
body: null,
error: error.name === "AbortError" ? `request timed out after ${timeoutMs}ms` : error.message
};
} finally {
clearTimeout(timer);
}
}
function doWriteRequest(value) {
return {
action: "do.write",
gatewayId: M3_IO_CHAIN.sourceGatewayId,
resourceId: M3_IO_CHAIN.sourceResourceId,
boxId: M3_IO_CHAIN.sourceBoxId,
port: M3_IO_CHAIN.sourcePort,
value
};
}
function diReadRequest() {
return {
action: "di.read",
gatewayId: M3_IO_CHAIN.targetGatewayId,
resourceId: M3_IO_CHAIN.targetResourceId,
boxId: M3_IO_CHAIN.targetBoxId,
port: M3_IO_CHAIN.targetPort
};
}
function routeChainMatches(chain) {
return chain?.sourceGatewayId === M3_IO_CHAIN.sourceGatewayId &&
chain?.sourceResourceId === M3_IO_CHAIN.sourceResourceId &&
chain?.sourceBoxId === M3_IO_CHAIN.sourceBoxId &&
chain?.sourcePort === M3_IO_CHAIN.sourcePort &&
chain?.targetGatewayId === M3_IO_CHAIN.targetGatewayId &&
chain?.targetResourceId === M3_IO_CHAIN.targetResourceId &&
chain?.targetBoxId === M3_IO_CHAIN.targetBoxId &&
chain?.targetPort === M3_IO_CHAIN.targetPort &&
chain?.patchPanelServiceId === M3_IO_CHAIN.patchPanelServiceId;
}
function checkResult(id, ok, summary, details = {}) {
return {
id,
status: ok ? "pass" : "fail",
sourceKind: details.evidenceLevel ?? labels.source,
summary,
...details
};
}
function literalFetchTargets(source) {
return [...source.matchAll(/\b(?:fetch|fetchJson)\(\s*(["'`])([^"'`]+)\1/gu)]
.map((match) => match[2])
.filter((target) => !target.includes("${"));
}
function functionBody(source, functionName) {
const match = source.match(new RegExp(`function\\s+${escapeRegExp(functionName)}\\s*\\([^)]*\\)\\s*\\{`, "u"));
if (!match) return "";
let depth = 0;
for (let index = match.index + match[0].length - 1; index < source.length; index += 1) {
const char = source[index];
if (char === "{") depth += 1;
if (char === "}") {
depth -= 1;
if (depth === 0) return source.slice(match.index, index + 1);
}
}
return "";
}
function resultTextForClassification(classification) {
if (classification === "trusted_green") {
return "DEV-LIVE M3 IO control path and durable trusted records are green.";
}
if (classification === controlPathReachablePersistenceBlocked) {
return "DEV-LIVE control path was reachable, but runtime durable/trusted persistence was blocked; not trusted green.";
}
if (classification === "control_path_blocked") {
return "DEV-LIVE M3 IO control path returned a blocked or schema-invalid operation.";
}
if (classification === "control_path_unreachable") {
return "DEV-LIVE M3 IO control endpoint was unreachable or non-2xx.";
}
if (classification === "source_contract_failed") {
return "SOURCE/static contract failed.";
}
return "SOURCE/static M3 IO control contract passed; DEV-LIVE was not run.";
}
function evidenceLabelDescriptions() {
return [
{
label: labels.source,
meaning: "Checked-in source or static contract; no runtime mutation."
},
{
label: labels.local,
meaning: "Local/in-process fixture evidence only; not a live DEV claim."
},
{
label: labels.dryRun,
meaning: "Planned sequence or schema validation; no live DEV POST."
},
{
label: labels.devLive,
meaning: "Explicitly gated DEV HTTP execution through cloud-web/cloud-api only."
},
{
label: labels.blocked,
meaning: "Cannot promote to trusted green."
}
];
}
function redactLiveRequest(body) {
return {
action: body.action,
gatewayId: body.gatewayId,
resourceId: body.resourceId,
boxId: body.boxId,
port: body.port,
value: Object.hasOwn(body, "value") ? body.value : undefined,
projectId: body.projectId,
traceId: body.traceId,
requestId: body.requestId,
actorId: body.actorId
};
}
function stringValue(value) {
return typeof value === "string" && value.trim().length > 0;
}
function parsePositiveInteger(value, fallback) {
if (value === undefined) return fallback;
const parsed = Number.parseInt(value, 10);
if (!Number.isInteger(parsed) || parsed <= 0) {
throw new Error("numeric value must be a positive integer");
}
return parsed;
}
async function readRepoText(root, relativePath) {
return readFile(path.join(root, relativePath), "utf8");
}
async function writeReport(reportPath, report) {
const absolute = path.resolve(repoRoot, reportPath);
if (!absolute.startsWith(`${repoRoot}${path.sep}`)) {
throw new Error("report path must stay inside the repository");
}
await mkdir(path.dirname(absolute), { recursive: true });
await writeFile(absolute, `${JSON.stringify(report, null, 2)}\n`);
}
function isoNow() {
return new Date().toISOString();
}
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}