#!/usr/bin/env node import assert from "node:assert/strict"; import crypto from "node:crypto"; import { readFile, stat } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { DEV_ENDPOINT, ENVIRONMENT_DEV } from "../internal/protocol/index.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const planPath = "fixtures/mvp/m5-e2e/dry-run-plan.json"; const checklistPath = "fixtures/mvp/m5-e2e/dev-acceptance-checklist.json"; const matrixPath = "docs/reference/MVP-e2e-acceptance.md"; const runtimePath = "fixtures/mvp/runtime.json"; const contractPath = "protocol/mvp-e2e-contract.md"; const commonSchemaPath = "protocol/schemas/common.json"; const JSON_RPC_VERSION = "2.0"; let serviceIds = []; let serviceIdSet = new Set(); const requiredArtifactFields = [ "serviceId", "commitId", "image", "tag", "digest", "buildSource", "deployEnv", "healthTimestamp" ]; const requiredStepSequence = [ "step-dev-health", "step-gateway-box-status", "step-wiring", "step-direct-hardware-call", "step-audit", "step-agent-session", "step-agent-hardware-call", "step-evidence", "step-cleanup" ]; const requiredTraceStates = ["accepted", "dispatched", "executed", "completed"]; function readJSON(relativePath) { return readFile(path.join(repoRoot, relativePath), "utf8").then((raw) => JSON.parse(raw)); } function byKey(items, key) { const map = new Map(); for (const item of items) { assert.ok(item && typeof item === "object", `${key} item must be an object`); assert.ok(item[key], `${key} item missing ${key}`); assert.equal(map.has(item[key]), false, `${key} ${item[key]} must be unique`); map.set(item[key], item); } return map; } function requireDevObject(label, object, environmentField = "environment") { assert.equal(object[environmentField], ENVIRONMENT_DEV, `${label} ${environmentField}`); } function requireTimestamp(label, value) { assert.equal(typeof value, "string", `${label} timestamp must be a string`); assert.equal(Number.isNaN(Date.parse(value)), false, `${label} timestamp must parse`); } function requireServiceId(label, serviceId) { assert.equal(serviceIdSet.has(serviceId), true, `${label} unknown serviceId ${serviceId}`); } function assertArrayIncludesAll(label, actual, expected) { assert.ok(Array.isArray(actual), `${label} must be an array`); for (const item of expected) { assert.equal(actual.includes(item), true, `${label} missing ${item}`); } } function assertNoProdTarget(value, trail = "plan") { if (typeof value === "string") { assert.equal(value.includes(":6666"), false, `${trail} must not target deprecated legacy port`); assert.equal(/\bprod\b/i.test(value), false, `${trail} must not target PROD`); return; } if (Array.isArray(value)) { value.forEach((item, index) => assertNoProdTarget(item, `${trail}[${index}]`)); return; } if (value && typeof value === "object") { for (const [key, nested] of Object.entries(value)) { if ( key === "prohibitedActions" || key === "failureCriteria" || key === "failRule" || key === "digest" || key === "sha256" ) { continue; } assertNoProdTarget(nested, `${trail}.${key}`); } } } function assertObjectHasOnlyDryRunSafety(plan) { assert.equal(plan.mode, "dry-run", "plan mode"); assert.equal(plan.safety.allowNetwork, false, "network must be disabled"); assert.equal(plan.safety.allowDeploy, false, "deploy must be disabled"); assert.equal(plan.safety.allowBrowserE2E, false, "browser e2e must be disabled"); assert.equal(plan.safety.allowSecrets, false, "secret reads must be disabled"); assertArrayIncludesAll("prohibited actions", plan.safety.prohibitedActions, [ "real-dev-deploy", "prod-deploy", "prod-smoke", "browser-e2e", "heavyweight-e2e", "secret-or-token-read", "force-push", "unidesk-runtime-substitution" ]); } function validateMeta(meta) { assert.ok(meta && typeof meta === "object" && !Array.isArray(meta), "meta must be an object"); assert.ok(meta.traceId, "meta.traceId is required"); requireServiceId("meta.serviceId", meta.serviceId); assert.equal(meta.environment, ENVIRONMENT_DEV, "meta.environment"); } function validateRequest(envelope) { assert.ok(envelope && typeof envelope === "object" && !Array.isArray(envelope), "request must be an object"); assert.equal(envelope.jsonrpc, JSON_RPC_VERSION, "request jsonrpc"); assert.notEqual(envelope.id, undefined, "request id"); assert.match(envelope.method, /^[a-z][a-z0-9]*(\.[a-z][a-z0-9_]*)+$/, "request method"); if (Object.hasOwn(envelope, "params")) { assert.ok( envelope.params && typeof envelope.params === "object" && !Array.isArray(envelope.params), "request params must be an object" ); } validateMeta(envelope.meta); } function validateResponse(envelope) { assert.ok(envelope && typeof envelope === "object" && !Array.isArray(envelope), "response must be an object"); assert.equal(envelope.jsonrpc, JSON_RPC_VERSION, "response jsonrpc"); assert.notEqual(envelope.id, undefined, "response id"); const hasResult = Object.hasOwn(envelope, "result"); const hasError = Object.hasOwn(envelope, "error"); assert.notEqual(hasResult, hasError, "response must contain exactly one of result or error"); if (hasError) { assert.ok(envelope.error && typeof envelope.error === "object", "response error must be an object"); assert.equal(Number.isInteger(envelope.error.code), true, "response error code"); assert.ok(envelope.error.message, "response error message"); } validateMeta(envelope.meta); } function assertEndpointFreeze(plan, checklist, runtime, contract) { assert.equal(plan.environment, ENVIRONMENT_DEV, "plan environment"); assert.equal(plan.endpoint, DEV_ENDPOINT, "plan endpoint"); assert.equal(checklist.environment, ENVIRONMENT_DEV, "checklist environment"); assert.equal(checklist.endpoint, DEV_ENDPOINT, "checklist endpoint"); assert.equal(runtime.endpoints.dev, DEV_ENDPOINT, "runtime fixture endpoint"); assert.equal(contract.includes(DEV_ENDPOINT), true, "MVP contract must include DEV endpoint"); assertNoProdTarget(plan); } function assertArtifacts(plan, checklist) { assert.deepEqual(checklist.artifactObservabilityFields, requiredArtifactFields); const artifacts = byKey(plan.artifacts, "serviceId"); const requiredServiceIds = new Set([ "hwlab-edge-proxy", "hwlab-tunnel-client", "hwlab-router", "hwlab-cloud-api", "hwlab-cloud-web", "hwlab-gateway", "hwlab-gateway-simu", "hwlab-box-simu", "hwlab-patch-panel", "hwlab-agent-mgr", "hwlab-agent-worker", "hwlab-agent-skills", "hwlab-cli" ]); for (const serviceId of requiredServiceIds) { assert.equal(artifacts.has(serviceId), true, `artifact missing ${serviceId}`); } for (const artifact of artifacts.values()) { requireServiceId(`artifact ${artifact.serviceId}`, artifact.serviceId); for (const field of requiredArtifactFields) { assert.ok(artifact[field], `artifact ${artifact.serviceId} missing ${field}`); } assert.equal(artifact.deployEnv, ENVIRONMENT_DEV, `artifact ${artifact.serviceId} deployEnv`); requireTimestamp(`artifact ${artifact.serviceId}.healthTimestamp`, artifact.healthTimestamp); if (artifact.digest === "not_applicable") { assert.ok( artifact.digestNotApplicableReason, `artifact ${artifact.serviceId} must explain not_applicable digest` ); } else { assert.match(artifact.digest, /^sha256:[a-f0-9]{64}$/, `artifact ${artifact.serviceId} digest`); } } return artifacts; } function assertHealth(plan, checklist, artifacts) { const healthById = byKey(plan.health, "healthId"); const healthByComponent = byKey(plan.health, "component"); const healthByService = new Map(); for (const health of plan.health) { if (!healthByService.has(health.serviceId)) { healthByService.set(health.serviceId, health); } } for (const contract of checklist.healthContracts) { const health = healthByComponent.get(contract.component); assert.ok(health, `health missing component ${contract.component}`); assert.equal(health.serviceId, contract.serviceId, `health ${contract.component} serviceId`); assert.equal(artifacts.has(health.serviceId), true, `health ${contract.component} missing artifact`); assert.equal(health.status, "healthy", `health ${contract.component} status`); assert.equal(health.deployEnv, ENVIRONMENT_DEV, `health ${contract.component} deployEnv`); assert.equal(health.dryRun, true, `health ${contract.component} dryRun`); requireTimestamp(`health ${contract.component}`, health.healthTimestamp); } assert.equal(healthByService.get("hwlab-edge-proxy").endpoint, `${DEV_ENDPOINT}/health`); assert.equal(healthByService.get("hwlab-cloud-web").output.directHardwareCall, false); assert.equal(healthByService.get("hwlab-gateway").output.patchPanelRequired, true); assert.equal(healthByService.get("hwlab-box-simu").output.patchPanelOnlyPropagation, true); assert.equal(healthByService.get("hwlab-agent-mgr").output.workerLeak, false); return { byId: healthById, byService: healthByService, count: plan.health.length }; } function assertAcceptanceSteps(plan, checklist) { const checklistStepIds = new Set(checklist.smokeSteps.map((step) => step.id)); assert.deepEqual( checklist.smokeSteps.map((step) => step.order), checklist.smokeSteps.map((_, index) => index + 1), "checklist smoke steps must be ordered" ); const referencedStepIds = new Set(); for (const step of plan.steps) { for (const acceptanceStepId of step.acceptanceStepIds) { assert.equal( checklistStepIds.has(acceptanceStepId), true, `${step.id} references unknown acceptance step ${acceptanceStepId}` ); referencedStepIds.add(acceptanceStepId); } } assertArrayIncludesAll("dry-run acceptance coverage", [...referencedStepIds], [ "static-contract-parse", "endpoint-freeze", "dev-ingress-health", "edge-route", "frp-tunnel", "d601-router", "cloud-surface", "gateway-sim-patch-panel", "agent-runtime", "artifact-observability" ]); } function assertStepGraph(plan) { const steps = plan.steps.slice().sort((a, b) => a.order - b.order); assert.deepEqual( steps.map((step) => step.id), requiredStepSequence, "M5 sequence must cover required dry-run flow" ); assert.deepEqual(plan.expectedSequence, requiredStepSequence, "expectedSequence must match required sequence"); const seen = new Set(); for (const step of steps) { assert.equal(seen.has(step.id), false, `duplicate step ${step.id}`); assert.ok(Array.isArray(step.inputs) && step.inputs.length > 0, `${step.id} inputs`); assert.ok(Array.isArray(step.outputs) && step.outputs.length > 0, `${step.id} outputs`); for (const dependency of step.requires) { assert.equal(seen.has(dependency), true, `${step.id} requires ${dependency} before it is produced`); } seen.add(step.id); } } function assertProjectGatewayAndBoxes(plan) { requireDevObject("project", plan.project); assert.equal(plan.project.status, "active", "project status"); const gatewaySessions = byKey(plan.gatewaySessions, "gatewaySessionId"); assert.equal(gatewaySessions.has("gws_m5-0001"), true, "real gateway boundary session is required"); assert.equal(gatewaySessions.has("gws_m5-simu-0001"), true, "gateway simulator session is required"); for (const session of gatewaySessions.values()) { assert.equal(session.projectId, plan.project.projectId, `gateway ${session.gatewaySessionId} project`); assert.ok(["hwlab-gateway", "hwlab-gateway-simu"].includes(session.serviceId), "gateway serviceId"); assert.equal(session.status, "connected", `gateway ${session.gatewaySessionId} status`); requireDevObject(`gateway ${session.gatewaySessionId}`, session); } const resources = byKey(plan.boxResources, "resourceId"); const capabilities = byKey(plan.boxCapabilities, "capabilityId"); for (const resource of resources.values()) { assert.equal(resource.projectId, plan.project.projectId, `resource ${resource.resourceId} project`); assert.equal(gatewaySessions.has(resource.gatewaySessionId), true, `resource ${resource.resourceId} gateway`); assert.equal(resource.state, "available", `resource ${resource.resourceId} state`); requireDevObject(`resource ${resource.resourceId}`, resource); } for (const capability of capabilities.values()) { assert.equal(capability.projectId, plan.project.projectId, `capability ${capability.capabilityId} project`); assert.equal(resources.has(capability.resourceId), true, `capability ${capability.capabilityId} resource`); assert.match(capability.name, /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/); } return { gatewaySessions, resources, capabilities }; } function assertWiring(plan, { gatewaySessions, resources, capabilities }) { const wiring = plan.wiringConfig; assert.equal(wiring.projectId, plan.project.projectId, "wiring project"); assert.equal(gatewaySessions.has(wiring.gatewaySessionId), true, "wiring gateway"); assert.equal(wiring.status, "active", "wiring status"); assert.equal(wiring.constraints.patchPanelOwnsRouting, true, "wiring must be patch-panel-owned"); assert.equal(wiring.constraints.directBoxBypassAllowed, false, "wiring must reject direct box bypass"); for (const connection of wiring.connections) { for (const side of ["from", "to"]) { const endpoint = connection[side]; assert.equal(resources.has(endpoint.resourceId), true, `wiring ${side} resource`); assert.equal(capabilities.has(endpoint.capabilityId), true, `wiring ${side} capability`); assert.equal( capabilities.get(endpoint.capabilityId).resourceId, endpoint.resourceId, `wiring ${side} capability must belong to resource` ); } } const patch = plan.patchPanelStatus; assert.equal(patch.projectId, plan.project.projectId, "patch project"); assert.equal(patch.gatewaySessionId, wiring.gatewaySessionId, "patch gateway"); assert.equal(patch.wiringConfigId, wiring.wiringConfigId, "patch wiring"); assert.equal(patch.serviceId, "hwlab-patch-panel", "patch service"); assert.equal(patch.state, "active", "patch state"); requireDevObject("patch", patch); requireTimestamp("patch observedAt", patch.observedAt); const activeConnectionKeys = new Set( patch.activeConnections.map( (connection) => `${connection.fromResourceId}:${connection.fromPort}->${connection.toResourceId}:${connection.toPort}` ) ); for (const connection of wiring.connections) { const key = `${connection.from.resourceId}:${connection.from.port}->${connection.to.resourceId}:${connection.to.port}`; assert.equal(activeConnectionKeys.has(key), true, `patch panel missing active connection ${key}`); } } function assertRpcAndOperations(plan, { gatewaySessions, resources, capabilities }) { const operations = byKey(plan.operations, "operationId"); const rpcByName = byKey(plan.rpc, "name"); for (const rpc of rpcByName.values()) { validateRequest(rpc.request); validateResponse(rpc.response); assert.equal(rpc.request.meta.environment, ENVIRONMENT_DEV, `${rpc.name} request env`); assert.equal(rpc.response.meta.environment, ENVIRONMENT_DEV, `${rpc.name} response env`); assert.equal(rpc.request.meta.traceId, rpc.response.meta.traceId, `${rpc.name} trace continuity`); assert.equal(rpc.request.params.dryRun, undefined, `${rpc.name} params must not use top-level dryRun`); assert.equal(rpc.request.params.input.dryRun, true, `${rpc.name} input dryRun`); assert.equal(rpc.response.result.operationId, rpc.operationId, `${rpc.name} response operation`); assert.equal(operations.has(rpc.operationId), true, `${rpc.name} operation exists`); } assert.equal(rpcByName.has("direct-hardware-request"), true, "direct RPC is required"); assert.equal(rpcByName.has("agent-hardware-request"), true, "agent RPC is required"); for (const operation of operations.values()) { assert.equal(operation.projectId, plan.project.projectId, `operation ${operation.operationId} project`); assert.equal(gatewaySessions.has(operation.gatewaySessionId), true, `operation ${operation.operationId} gateway`); assert.equal(resources.has(operation.resourceId), true, `operation ${operation.operationId} resource`); assert.equal(capabilities.has(operation.capabilityId), true, `operation ${operation.operationId} capability`); assert.equal(operation.status, "succeeded", `operation ${operation.operationId} status`); assert.equal(operation.input.dryRun, true, `operation ${operation.operationId} dryRun`); assert.equal(operation.output.patchPanelStatusId, plan.patchPanelStatus.patchPanelStatusId); requireDevObject(`operation ${operation.operationId}`, operation); } const agentOperation = operations.get("op_m5-agent-0001"); assert.equal(agentOperation.agentSessionId, plan.agentSession.agentSessionId, "agent operation session"); assert.equal(agentOperation.workerSessionId, plan.workerSession.workerSessionId, "agent operation worker"); return { operations, rpcByName }; } function assertAgent(plan, { gatewaySessions }) { const { agentSession, workerSession } = plan; assert.equal(agentSession.projectId, plan.project.projectId, "agent project"); assert.equal(agentSession.serviceId, "hwlab-agent-mgr", "agent service"); assert.equal(agentSession.status, "completed", "agent status"); assert.equal(agentSession.metadata.workerSessionId, workerSession.workerSessionId, "agent worker link"); requireDevObject("agent", agentSession); assert.equal(workerSession.agentSessionId, agentSession.agentSessionId, "worker agent link"); assert.equal(workerSession.projectId, plan.project.projectId, "worker project"); assert.equal(workerSession.serviceId, "hwlab-agent-worker", "worker service"); assert.equal(workerSession.status, "completed", "worker status"); assert.equal(gatewaySessions.has(workerSession.gatewaySessionId), true, "worker gateway"); assert.equal(workerSession.metadata.scopedProjectId, plan.project.projectId, "worker project scope"); requireDevObject("worker", workerSession); } function assertTraceAuditEvidenceAndCleanup(plan, { operations }) { const traceEvents = byKey(plan.traceEvents, "traceEventId"); const auditEvents = byKey(plan.auditEvents, "auditId"); const evidenceRecords = byKey(plan.evidenceRecords, "evidenceId"); const cleanupRecords = byKey(plan.cleanup, "cleanupId"); for (const trace of traceEvents.values()) { assert.equal(trace.projectId, plan.project.projectId, `trace ${trace.traceEventId} project`); requireServiceId(`trace ${trace.traceEventId}`, trace.serviceId); requireDevObject(`trace ${trace.traceEventId}`, trace); requireTimestamp(`trace ${trace.traceEventId}`, trace.occurredAt); if (trace.operationId) { assert.equal(operations.has(trace.operationId), true, `trace ${trace.traceEventId} operation`); } } for (const operationId of ["op_m5-direct-0001", "op_m5-agent-0001"]) { const states = plan.traceEvents .filter((trace) => trace.operationId === operationId) .map((trace) => trace.metadata.state); assert.deepEqual(states, requiredTraceStates, `${operationId} trace lifecycle`); } for (const audit of auditEvents.values()) { assert.equal(audit.projectId, plan.project.projectId, `audit ${audit.auditId} project`); requireServiceId(`audit ${audit.auditId}`, audit.serviceId); requireDevObject(`audit ${audit.auditId}`, audit); requireTimestamp(`audit ${audit.auditId}`, audit.occurredAt); assert.ok(audit.outcome, `audit ${audit.auditId} outcome`); } assert.equal( [...auditEvents.values()].some((audit) => audit.action === "hardware.operation.request"), true, "hardware operation audit required" ); assert.equal(auditEvents.has("aud_m5-cleanup"), true, "cleanup audit required"); for (const evidence of evidenceRecords.values()) { assert.equal(evidence.projectId, plan.project.projectId, `evidence ${evidence.evidenceId} project`); assert.equal(operations.has(evidence.operationId), true, `evidence ${evidence.evidenceId} operation`); requireServiceId(`evidence ${evidence.evidenceId}`, evidence.serviceId); requireDevObject(`evidence ${evidence.evidenceId}`, evidence); requireTimestamp(`evidence ${evidence.evidenceId}`, evidence.createdAt); } for (const operationId of operations.keys()) { assert.equal( [...evidenceRecords.values()].some((evidence) => evidence.operationId === operationId), true, `evidence missing for ${operationId}` ); } for (const cleanup of cleanupRecords.values()) { assert.equal(cleanup.status, "completed", `cleanup ${cleanup.cleanupId} status`); assert.equal(cleanup.environment, ENVIRONMENT_DEV, `cleanup ${cleanup.cleanupId} environment`); requireServiceId(`cleanup ${cleanup.cleanupId}`, cleanup.serviceId); requireTimestamp(`cleanup ${cleanup.cleanupId}`, cleanup.completedAt); } assert.equal(cleanupRecords.get("cln_m5-worker").outputs.workspaceReleased, true, "worker cleanup release"); assert.equal(cleanupRecords.get("cln_m5-gateway-state").outputs.transientStateCleared, true); return { traceEvents, auditEvents, evidenceRecords, cleanupRecords }; } async function assertEvidenceFiles(evidenceRecords) { for (const evidence of evidenceRecords.values()) { const absolutePath = path.join(repoRoot, evidence.uri); const content = await readFile(absolutePath); const digest = crypto.createHash("sha256").update(content).digest("hex"); const stats = await stat(absolutePath); assert.equal(digest, evidence.sha256, `evidence ${evidence.evidenceId} sha256`); assert.equal(stats.size, evidence.sizeBytes, `evidence ${evidence.evidenceId} sizeBytes`); } } function assertStepInputsOutputs(plan, indexes) { const resolvers = { health: (id) => indexes.healthIds.has(id), gatewaySession: (id) => indexes.gatewaySessions.has(id), boxResource: (id) => indexes.resources.has(id), resource: (id) => indexes.resources.has(id), capability: (id) => indexes.capabilities.has(id), wiringConfig: (id) => id === plan.wiringConfig.wiringConfigId, patchPanelStatus: (id) => id === plan.patchPanelStatus.patchPanelStatusId, rpc: (id) => indexes.rpcByName.has(id), operation: (id) => indexes.operations.has(id), audit: (id) => indexes.auditEvents.has(id), agentSession: (id) => id === plan.agentSession.agentSessionId, workerSession: (id) => id === plan.workerSession.workerSessionId, trace: (id) => indexes.traceIds.has(id), evidence: (id) => indexes.evidenceRecords.has(id), cleanup: (id) => indexes.cleanupRecords.has(id) }; for (const step of plan.steps) { for (const input of step.inputs) { if (input === "endpoint") { continue; } const [type, id] = input.split(":"); assert.ok(type && id, `${step.id} input ${input} must be typed`); assert.ok(resolvers[type], `${step.id} input ${input} has unknown type ${type}`); assert.equal(resolvers[type](id), true, `${step.id} input ${input} does not resolve`); } } const allOutputs = new Set([ "route_m5-dev", "tun_m5-d601", "hwlab-dev", "hwlab-cloud-api", "hwlab-cloud-web", ...indexes.gatewaySessions.keys(), ...indexes.resources.keys(), ...indexes.capabilities.keys(), plan.wiringConfig.wiringConfigId, plan.patchPanelStatus.patchPanelStatusId, ...indexes.operations.keys(), ...indexes.traceIds, ...indexes.auditEvents.keys(), plan.agentSession.agentSessionId, plan.workerSession.workerSessionId, ...indexes.evidenceRecords.keys(), ...indexes.cleanupRecords.keys() ]); for (const step of plan.steps) { for (const output of step.outputs) { assert.equal(allOutputs.has(output), true, `${step.id} output ${output} does not resolve`); } } } function assertDependencies(plan) { assertArrayIncludesAll( "M0-M4 dependencies", plan.m0ToM4Dependencies.map((dependency) => dependency.id), ["m0-contract-audit", "m1-local-smoke", "m2-cloud-core", "m3-hardware-loop", "m4-agent-runtime"] ); const dependencyStepIds = new Set(plan.steps.map((step) => step.id)); for (const dependency of plan.m0ToM4Dependencies) { assert.ok(dependency.provides.length > 0, `${dependency.id} provides`); assert.equal(dependency.status, "contract_only", `${dependency.id} status`); for (const stepId of dependency.requiredFor) { assert.equal(dependencyStepIds.has(stepId), true, `${dependency.id} references unknown ${stepId}`); } } assert.equal(plan.realDevGate.command, "hwlab-cli test e2e --env dev --mvp"); assert.equal(plan.realDevGate.requiresAllBlockersClear, true); assert.equal(plan.realDevGate.requiresDryRunPass, true); assert.equal(plan.realDevGate.requiresHumanApprovalForRealDev, true); } const [plan, checklist, runtime, contract, matrix, commonSchema] = await Promise.all([ readJSON(planPath), readJSON(checklistPath), readJSON(runtimePath), readFile(path.join(repoRoot, contractPath), "utf8"), readFile(path.join(repoRoot, matrixPath), "utf8"), readJSON(commonSchemaPath) ]); serviceIds = commonSchema.$defs.serviceId.enum; serviceIdSet = new Set(serviceIds); assert.equal(matrix.includes("HWLAB MVP E2E"), true, "MVP E2E reference exists"); assertObjectHasOnlyDryRunSafety(plan); assertEndpointFreeze(plan, checklist, runtime, contract); const artifacts = assertArtifacts(plan, checklist); const health = assertHealth(plan, checklist, artifacts); assertAcceptanceSteps(plan, checklist); assertStepGraph(plan); const topology = assertProjectGatewayAndBoxes(plan); assertWiring(plan, topology); assertAgent(plan, topology); const rpcAndOperations = assertRpcAndOperations(plan, topology); const evidenceIndexes = assertTraceAuditEvidenceAndCleanup(plan, rpcAndOperations); await assertEvidenceFiles(evidenceIndexes.evidenceRecords); assertStepInputsOutputs(plan, { ...topology, ...rpcAndOperations, ...evidenceIndexes, healthIds: new Set(health.byId.keys()), traceIds: new Set(plan.traceEvents.map((trace) => trace.traceId)) }); assertDependencies(plan); console.log( [ `M5 MVP E2E dry-run passed: ${plan.steps.length} steps`, `${artifacts.size} artifacts`, `${health.count} health contracts`, `${rpcAndOperations.operations.size} hardware operations`, `${evidenceIndexes.evidenceRecords.size} evidence records`, "0 network calls" ].join(", ") );