# M3 Hardware Trusted Loop Local Contract This contract smoke proves the local M3 wiring path for: ```text box-simu-1 DO1 -> box-simu-2 DI1 ``` The fixture is `fixtures/mvp/m3-hardware-loop/topology.json`. It contains two `hwlab-box-simu` resources, two `hwlab-gateway-simu` sessions, and one `hwlab-patch-panel` status object. The active wiring config has one exclusive connection from `res_boxsimu_1:DO1` to `res_boxsimu_2:DI1`. Run the smoke locally: ```sh node scripts/m3-hardware-loop-smoke.mjs ``` ## What It Checks - DEV-only topology with two boxes, two gateways, and one active patch panel. - Patch-panel-only propagation; box local loopback is not accepted as a cross-device substitute. - Direct local call into the existing patch-panel model routes `box-simu-1 DO1` to `box-simu-2 DI1`. - An unwired local signal does not cross devices. - The expected audit event includes the L0 audit fields: `auditId`, `traceId`, `actorType`, `actorId`, `action`, `targetType`, `targetId`, `serviceId`, `environment`, and `occurredAt`, plus project, gateway session, operation, outcome, and metadata fields. ## Boundary This is a local contract smoke only. It does not deploy DEV or PROD services, does not connect to real hardware, does not read secrets or tokens, and does not write `audit_events` rows to a database. The audit event in the fixture is an expected record shape for the future real flow. The later real DEV M3 path must replace the in-process direct call with live service boundaries: cloud request validation, gateway session ownership, patch-panel state application, real or simulator box observation, persisted audit events, and evidence records. That future work must preserve the same wiring contract and must keep routing decisions under `hwlab-patch-panel` ownership instead of mutating another box directly. ## L2 Local Runtime Smoke Issue 53 adds a runnable localhost-only L2 simulator contract: ```sh npm run l2:smoke ``` The smoke starts two `hwlab-box-simu` instances (`box-a`, `box-b`) and two `hwlab-gateway-simu` instances (`gateway-a`, `gateway-b`) on ephemeral localhost ports. It verifies: - box ports for `AI1`, `AO1`, `DI1`, `DO1`, and `FREQ1`; - internal port API writes for patch-panel-owned delivery; - gateway register, heartbeat, capability report, and invoke forwarding; - multi-instance identity separation for `box-a`/`box-b` and `gateway-a`/`gateway-b`; - HWLAB JSON-RPC error codes for missing gateway registration and unavailable capabilities; - returned audit and evidence object shapes. This smoke is still not a DEV deploy or PROD deploy. It proves the minimum runtime surface that later M3/M5 live checks can call. Cross-device propagation is not implemented as box-simu loopback; the only accepted cross-device owner remains `hwlab-patch-panel`, which calls the box internal port API. ## DEV Runtime Smoke Use the no-write plan first: ```sh node scripts/dev-m3-hardware-loop-smoke.mjs --dry-run ``` The dry-run plan writes `reports/dev-gate/dev-m3-hardware-loop.json` by default, records `dryRunPlan.evidenceLevel: "DRY-RUN"`, lists the live write preconditions, enumerates the endpoints that a later live run would call, and keeps `liveOperation.status: "not_run"`. It does not call DEV endpoints, does not send `/ports/write` or `/signals/route`, and cannot be promoted to `DEV-LIVE`. Use this command only for the approved DEV simulator runtime window: ```sh node scripts/dev-m3-hardware-loop-smoke.mjs --live --confirm-dev --expect-non-prod ``` The legacy `--confirmed-non-production` flag is accepted as compatibility, but new handoffs should use `--expect-non-prod`. The script refuses the default mode and refuses `--dry-run` combined with `--live`. The live smoke writes `reports/dev-gate/dev-m3-hardware-loop.json`. It first checks the frozen DEV endpoint, `http://74.48.78.17:16667`, and stops at the first critical blocker from `docs/dev-acceptance-matrix.md`. When DEV ingress is not observable, the report is `blocked` and the script does not run the live `do.write true -> di.read true` operation. If DEV ingress is reachable, the script requires explicit direct DEV simulator targets through these environment variables: ```text HWLAB_DEV_BOX_SIMU_1_URL HWLAB_DEV_BOX_SIMU_2_URL HWLAB_DEV_GATEWAY_SIMU_1_URL HWLAB_DEV_GATEWAY_SIMU_2_URL HWLAB_DEV_PATCH_PANEL_URL ``` Those targets must be two distinct HWLAB DEV simulator services for each simulator kind, plus one HWLAB DEV patch panel, not UniDesk substitutes. The script then checks two box simulators, two gateway simulators, active patch-panel wiring for `res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1`, a live direct call that reads `DI1=true`, and returned operation, trace, audit, and evidence identifiers. Fixture-backed local M3 output is never accepted as live DEV evidence. ## Read-Only Continuation Evidence When DEV ingress is blocked, continuation work may still record read-only evidence. The smoke report includes a static comparison of `deploy/k8s/base/workloads.yaml` against the M3 requirement for two `hwlab-box-simu` instances and two `hwlab-gateway-simu` instances. This is not live runtime proof. The checked-in DEV manifests are source-ready only when `node scripts/validate-dev-m3-cardinality.mjs` passes: two `hwlab-box-simu` replicas with `boxsimu_1,boxsimu_2`, two `hwlab-gateway-simu` replicas with `res_boxsimu_1,res_boxsimu_2`, and one `hwlab-patch-panel`. If the live runtime still lacks the required simulator cardinality, the fix is a separate authorized DEV deploy or runtime task, not a mutation inside the M3 evidence collection task.