fix: harden M3 simulator identities and wiring
This commit is contained in:
@@ -8,12 +8,22 @@ import {
|
||||
import { createHealth, createBoxState, selectInstanceValue } from "../../internal/sim/model.mjs";
|
||||
import { createJsonServer, listen, parsePort, readJson } from "../../internal/sim/http.mjs";
|
||||
|
||||
const boxId = selectInstanceValue(process.env.HWLAB_BOX_ID, "boxsim_alpha");
|
||||
const boxId = selectInstanceValue(process.env.HWLAB_BOX_ID, "boxsim_alpha", {
|
||||
requireIndexForMultiple: true,
|
||||
label: "HWLAB_BOX_ID"
|
||||
});
|
||||
const gatewaySessionId = selectInstanceValue(
|
||||
process.env.HWLAB_GATEWAY_SESSION_ID,
|
||||
"gws_mvp_simu"
|
||||
"gws_mvp_simu",
|
||||
{
|
||||
requireIndexForMultiple: true,
|
||||
label: "HWLAB_GATEWAY_SESSION_ID"
|
||||
}
|
||||
);
|
||||
const resourceId = selectInstanceValue(process.env.HWLAB_BOX_RESOURCE_ID, "");
|
||||
const resourceId = selectInstanceValue(process.env.HWLAB_BOX_RESOURCE_ID, "", {
|
||||
requireIndexForMultiple: true,
|
||||
label: "HWLAB_BOX_RESOURCE_ID"
|
||||
});
|
||||
const port = parsePort(process.env.PORT, 7201);
|
||||
const state = createBoxState({ boxId, gatewaySessionId });
|
||||
if (resourceId) {
|
||||
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
import { fetchJson, createJsonServer, listen, parsePort, readJson } from "../../internal/sim/http.mjs";
|
||||
import { createRpcErrorBody, ERROR_CODES } from "../../internal/protocol/index.mjs";
|
||||
|
||||
const gatewayId = selectInstanceValue(process.env.HWLAB_GATEWAY_ID, "gateway-a");
|
||||
const gatewayId = selectInstanceValue(process.env.HWLAB_GATEWAY_ID, "gateway-a", {
|
||||
requireIndexForMultiple: true,
|
||||
label: "HWLAB_GATEWAY_ID"
|
||||
});
|
||||
const port = parsePort(process.env.PORT, 7101);
|
||||
const endpoint = `http://127.0.0.1:${port}`;
|
||||
const boxUrls = parseBoxUrls(process.env.HWLAB_BOX_URLS);
|
||||
@@ -117,9 +120,9 @@ function parseList(value) {
|
||||
}
|
||||
|
||||
function parseBoxes({ resources, ids, urls, instanceScoped = false }) {
|
||||
const explicitResources = instanceScoped ? selectScopedList(resources) : parseList(resources);
|
||||
const explicitIds = instanceScoped ? selectScopedList(ids) : parseList(ids);
|
||||
const explicitUrls = instanceScoped ? selectScopedList(urls.join(",")) : urls;
|
||||
const explicitResources = instanceScoped ? selectScopedList(resources, "HWLAB_BOX_RESOURCES") : parseList(resources);
|
||||
const explicitIds = instanceScoped ? selectScopedList(ids, "HWLAB_BOX_IDS") : parseList(ids);
|
||||
const explicitUrls = instanceScoped ? selectScopedList(urls.join(","), "HWLAB_BOX_URLS") : urls;
|
||||
const count = Math.max(explicitResources.length, explicitIds.length, explicitUrls.length, 1);
|
||||
const parsed = [];
|
||||
|
||||
@@ -140,12 +143,15 @@ function parseBoxes({ resources, ids, urls, instanceScoped = false }) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function selectScopedList(value) {
|
||||
function selectScopedList(value, label = "scoped list") {
|
||||
const items = parseList(value);
|
||||
if (items.length <= 1) {
|
||||
return items;
|
||||
}
|
||||
const selected = selectInstanceValue(items.join(","), "");
|
||||
const selected = selectInstanceValue(items.join(","), "", {
|
||||
requireIndexForMultiple: true,
|
||||
label
|
||||
});
|
||||
return selected ? [selected] : [];
|
||||
}
|
||||
|
||||
|
||||
@@ -13,12 +13,13 @@ const topology = createM3Topology();
|
||||
const endpointMap = parseEndpointMap(process.env.HWLAB_PATCH_PANEL_ENDPOINT_MAP) ?? {
|
||||
res_boxsimu_2: "http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201"
|
||||
};
|
||||
const manifestWiringConfig = parseWiringConfig(process.env.HWLAB_PATCH_PANEL_WIRING_CONFIG);
|
||||
const runtime = wiringConfigPath
|
||||
? await createPatchPanelRuntimeFromFile({ path: wiringConfigPath })
|
||||
: createPatchPanelRuntime({
|
||||
wiringConfig: topology.wiringConfig,
|
||||
wiringConfig: manifestWiringConfig ?? topology.wiringConfig,
|
||||
endpointMap,
|
||||
configSource: "internal:m3-topology"
|
||||
configSource: manifestWiringConfig ? "env:HWLAB_PATCH_PANEL_WIRING_CONFIG" : "internal:m3-topology"
|
||||
});
|
||||
|
||||
const routes = new Map();
|
||||
@@ -51,3 +52,12 @@ function parseEndpointMap(value) {
|
||||
|
||||
return JSON.parse(source);
|
||||
}
|
||||
|
||||
function parseWiringConfig(value) {
|
||||
const source = String(value ?? "").trim();
|
||||
if (!source) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSON.parse(source);
|
||||
}
|
||||
|
||||
@@ -301,9 +301,17 @@
|
||||
"healthPath": "/health/live",
|
||||
"profile": "dev",
|
||||
"replicas": 1,
|
||||
"m3Route": {
|
||||
"fromResourceId": "res_boxsimu_1",
|
||||
"fromPort": "DO1",
|
||||
"patchPanelServiceId": "hwlab-patch-panel",
|
||||
"toResourceId": "res_boxsimu_2",
|
||||
"toPort": "DI1"
|
||||
},
|
||||
"env": {
|
||||
"HWLAB_PATCH_PANEL_MODE": "dev-skeleton",
|
||||
"HWLAB_PATCH_PANEL_ENDPOINT_MAP": "{\"res_boxsimu_2\":\"http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201\"}",
|
||||
"HWLAB_PATCH_PANEL_WIRING_CONFIG": "{\"wiringConfigId\":\"wir_m3_do1_di1\",\"projectId\":\"prj_m3_hardware_loop\",\"gatewaySessionId\":\"gws_gwsimu_1\",\"name\":\"M3 simulated DO1 to DI1 patch wiring\",\"status\":\"active\",\"connections\":[{\"from\":{\"resourceId\":\"res_boxsimu_1\",\"port\":\"DO1\"},\"to\":{\"resourceId\":\"res_boxsimu_2\",\"port\":\"DI1\"},\"mode\":\"exclusive\"}],\"constraints\":{\"propagation\":\"patch-panel-only\",\"localLoopbackSubstituteAllowed\":false},\"createdAt\":\"2026-05-21T00:00:00.000Z\",\"updatedAt\":\"2026-05-21T00:00:00.000Z\"}",
|
||||
"HWLAB_ENVIRONMENT": "dev"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -353,6 +353,33 @@
|
||||
"type": "string",
|
||||
"enum": ["indexed-workloads"]
|
||||
},
|
||||
"m3Route": {
|
||||
"type": "object",
|
||||
"required": ["fromResourceId", "fromPort", "patchPanelServiceId", "toResourceId", "toPort"],
|
||||
"properties": {
|
||||
"fromResourceId": {
|
||||
"type": "string",
|
||||
"const": "res_boxsimu_1"
|
||||
},
|
||||
"fromPort": {
|
||||
"type": "string",
|
||||
"const": "DO1"
|
||||
},
|
||||
"patchPanelServiceId": {
|
||||
"type": "string",
|
||||
"const": "hwlab-patch-panel"
|
||||
},
|
||||
"toResourceId": {
|
||||
"type": "string",
|
||||
"const": "res_boxsimu_2"
|
||||
},
|
||||
"toPort": {
|
||||
"type": "string",
|
||||
"const": "DI1"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
|
||||
@@ -543,6 +543,10 @@
|
||||
{
|
||||
"name": "HWLAB_PATCH_PANEL_ENDPOINT_MAP",
|
||||
"value": "{\"res_boxsimu_2\":\"http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201\"}"
|
||||
},
|
||||
{
|
||||
"name": "HWLAB_PATCH_PANEL_WIRING_CONFIG",
|
||||
"value": "{\"wiringConfigId\":\"wir_m3_do1_di1\",\"projectId\":\"prj_m3_hardware_loop\",\"gatewaySessionId\":\"gws_gwsimu_1\",\"name\":\"M3 simulated DO1 to DI1 patch wiring\",\"status\":\"active\",\"connections\":[{\"from\":{\"resourceId\":\"res_boxsimu_1\",\"port\":\"DO1\"},\"to\":{\"resourceId\":\"res_boxsimu_2\",\"port\":\"DI1\"},\"mode\":\"exclusive\"}],\"constraints\":{\"propagation\":\"patch-panel-only\",\"localLoopbackSubstituteAllowed\":false},\"createdAt\":\"2026-05-21T00:00:00.000Z\",\"updatedAt\":\"2026-05-21T00:00:00.000Z\"}"
|
||||
}
|
||||
],
|
||||
"readinessProbe": {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { createM3Topology } from "./fixture.mjs";
|
||||
import { createWiringConfig } from "./model.mjs";
|
||||
import { createPatchPanelRuntime } from "./runtime.mjs";
|
||||
|
||||
@@ -110,3 +111,55 @@ test("runtime reports missing target endpoint as readable diagnostics", async ()
|
||||
assert.equal(result.deliveryCount, 1);
|
||||
assert.ok(result.diagnostics.some((diagnostic) => diagnostic.code === "target_endpoint_missing"));
|
||||
});
|
||||
|
||||
test("runtime applies first-class DEV M3 wiring config for res_boxsimu_1 DO1 to res_boxsimu_2 DI1", async () => {
|
||||
const topology = createM3Topology();
|
||||
const writes = [];
|
||||
const runtime = createPatchPanelRuntime({
|
||||
wiringConfig: topology.wiringConfig,
|
||||
endpointMap: {
|
||||
res_boxsimu_2: "http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201"
|
||||
},
|
||||
configSource: "env:HWLAB_PATCH_PANEL_WIRING_CONFIG",
|
||||
now: () => "2026-05-21T00:00:01.000Z",
|
||||
requestJson: async (url, body) => {
|
||||
writes.push({ url, body });
|
||||
return {
|
||||
status: 200,
|
||||
json: {
|
||||
accepted: true
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(runtime.status().state, "active");
|
||||
assert.equal(runtime.status().metadata.configSource, "env:HWLAB_PATCH_PANEL_WIRING_CONFIG");
|
||||
assert.deepEqual(runtime.status().activeConnections, [
|
||||
{
|
||||
fromResourceId: "res_boxsimu_1",
|
||||
fromPort: "DO1",
|
||||
toResourceId: "res_boxsimu_2",
|
||||
toPort: "DI1"
|
||||
}
|
||||
]);
|
||||
|
||||
const result = await runtime.routeSignal({
|
||||
fromResourceId: "res_boxsimu_1",
|
||||
fromPort: "DO1",
|
||||
value: true
|
||||
});
|
||||
|
||||
assert.equal(result.accepted, true);
|
||||
assert.equal(result.propagatedBy, "hwlab-patch-panel");
|
||||
assert.equal(result.deliveryCount, 1);
|
||||
assert.equal(writes.length, 1);
|
||||
assert.equal(
|
||||
writes[0].url,
|
||||
"http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201/internal/ports/write"
|
||||
);
|
||||
assert.equal(writes[0].body.port, "DI1");
|
||||
assert.equal(writes[0].body.sourceResourceId, "res_boxsimu_1");
|
||||
assert.equal(writes[0].body.sourcePort, "DO1");
|
||||
assert.equal(writes[0].body.propagatedBy, "hwlab-patch-panel");
|
||||
});
|
||||
|
||||
+17
-3
@@ -92,7 +92,16 @@ function isoNow() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
export function selectInstanceValue(value, fallback, { index = process.env.HWLAB_INSTANCE_INDEX } = {}) {
|
||||
export function selectInstanceValue(
|
||||
value,
|
||||
fallback,
|
||||
{
|
||||
index = process.env.HWLAB_INSTANCE_INDEX,
|
||||
hostname = process.env.HOSTNAME,
|
||||
requireIndexForMultiple = false,
|
||||
label = "instance value"
|
||||
} = {}
|
||||
) {
|
||||
const items = String(value ?? "")
|
||||
.split(",")
|
||||
.map((item) => item.trim())
|
||||
@@ -110,12 +119,17 @@ export function selectInstanceValue(value, fallback, { index = process.env.HWLAB
|
||||
return items[parsedIndex];
|
||||
}
|
||||
|
||||
const hostname = process.env.HOSTNAME ?? "";
|
||||
const ordinal = Number.parseInt(hostname.match(/-(\d+)$/)?.[1] ?? "", 10);
|
||||
const ordinal = Number.parseInt(String(hostname ?? "").match(/-(\d+)$/)?.[1] ?? "", 10);
|
||||
if (Number.isInteger(ordinal) && ordinal >= 0 && ordinal < items.length) {
|
||||
return items[ordinal];
|
||||
}
|
||||
|
||||
if (requireIndexForMultiple) {
|
||||
throw new Error(
|
||||
`${label} declares ${items.length} indexed values, but neither HWLAB_INSTANCE_INDEX nor a StatefulSet ordinal HOSTNAME selected one`
|
||||
);
|
||||
}
|
||||
|
||||
return items[0];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { selectInstanceValue } from "./model.mjs";
|
||||
|
||||
test("indexed simulator identity selection uses explicit HWLAB_INSTANCE_INDEX", () => {
|
||||
assert.equal(
|
||||
selectInstanceValue("boxsimu_1,boxsimu_2", "fallback", {
|
||||
index: "1",
|
||||
hostname: "deployment-shaped-pod",
|
||||
requireIndexForMultiple: true,
|
||||
label: "HWLAB_BOX_ID"
|
||||
}),
|
||||
"boxsimu_2"
|
||||
);
|
||||
});
|
||||
|
||||
test("indexed simulator identity selection uses StatefulSet hostname ordinal", () => {
|
||||
assert.equal(
|
||||
selectInstanceValue("res_boxsimu_1,res_boxsimu_2", "fallback", {
|
||||
index: "",
|
||||
hostname: "hwlab-box-simu-1",
|
||||
requireIndexForMultiple: true,
|
||||
label: "HWLAB_BOX_RESOURCE_ID"
|
||||
}),
|
||||
"res_boxsimu_2"
|
||||
);
|
||||
});
|
||||
|
||||
test("multi-value simulator identities fail closed without an instance index", () => {
|
||||
assert.throws(
|
||||
() =>
|
||||
selectInstanceValue("gwsimu_1,gwsimu_2", "fallback", {
|
||||
index: "",
|
||||
hostname: "hwlab-gateway-simu-random-suffix",
|
||||
requireIndexForMultiple: true,
|
||||
label: "HWLAB_GATEWAY_ID"
|
||||
}),
|
||||
/HWLAB_GATEWAY_ID declares 2 indexed values/u
|
||||
);
|
||||
});
|
||||
|
||||
test("single simulator identity value still works without an index", () => {
|
||||
assert.equal(
|
||||
selectInstanceValue("boxsimu_1", "fallback", {
|
||||
index: "",
|
||||
hostname: "deployment-shaped-pod",
|
||||
requireIndexForMultiple: true,
|
||||
label: "HWLAB_BOX_ID"
|
||||
}),
|
||||
"boxsimu_1"
|
||||
);
|
||||
});
|
||||
+1
-1
@@ -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/server.mjs && node --check internal/sim/model.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/server.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/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/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",
|
||||
|
||||
@@ -419,6 +419,21 @@ async function collectReadOnlySupplementalEvidence() {
|
||||
const patchPanelEndpointMap = patchPanel?.env.HWLAB_PATCH_PANEL_ENDPOINT_MAP
|
||||
? JSON.parse(patchPanel.env.HWLAB_PATCH_PANEL_ENDPOINT_MAP)
|
||||
: {};
|
||||
const patchPanelWiringConfig = patchPanel?.env.HWLAB_PATCH_PANEL_WIRING_CONFIG
|
||||
? JSON.parse(patchPanel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG)
|
||||
: null;
|
||||
const patchPanelHasManifestRoute =
|
||||
patchPanelWiringConfig?.status === "active" &&
|
||||
patchPanelWiringConfig?.constraints?.propagation === "patch-panel-only" &&
|
||||
patchPanelWiringConfig?.constraints?.localLoopbackSubstituteAllowed === false &&
|
||||
(patchPanelWiringConfig?.connections ?? []).some(
|
||||
(connection) =>
|
||||
connection.from?.resourceId === requiredM3Connection.fromResourceId &&
|
||||
connection.from?.port === requiredM3Connection.fromPort &&
|
||||
connection.to?.resourceId === requiredM3Connection.toResourceId &&
|
||||
connection.to?.port === requiredM3Connection.toPort &&
|
||||
connection.mode === "exclusive"
|
||||
);
|
||||
const boxReadyForM3 =
|
||||
box?.kind === "StatefulSet" &&
|
||||
box?.replicas === 2 &&
|
||||
@@ -436,7 +451,13 @@ async function collectReadOnlySupplementalEvidence() {
|
||||
]);
|
||||
const patchPanelReadyForM3 =
|
||||
patchPanel?.replicas === 1 &&
|
||||
patchPanelEndpointMap.res_boxsimu_2 === "http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201";
|
||||
patchPanel?.m3Route?.fromResourceId === requiredM3Connection.fromResourceId &&
|
||||
patchPanel?.m3Route?.fromPort === requiredM3Connection.fromPort &&
|
||||
patchPanel?.m3Route?.patchPanelServiceId === "hwlab-patch-panel" &&
|
||||
patchPanel?.m3Route?.toResourceId === requiredM3Connection.toResourceId &&
|
||||
patchPanel?.m3Route?.toPort === requiredM3Connection.toPort &&
|
||||
patchPanelEndpointMap.res_boxsimu_2 === "http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201" &&
|
||||
patchPanelHasManifestRoute;
|
||||
const manifestReadyForM3 = boxReadyForM3 && gatewayReadyForM3 && patchPanelReadyForM3 && hasInstanceServices;
|
||||
|
||||
return [
|
||||
@@ -466,7 +487,8 @@ async function collectReadOnlySupplementalEvidence() {
|
||||
},
|
||||
"hwlab-patch-panel": {
|
||||
replicas: patchPanel?.replicas ?? null,
|
||||
env: patchPanel?.env ?? null
|
||||
env: patchPanel?.env ?? null,
|
||||
firstClassWiringConfig: patchPanelWiringConfig
|
||||
},
|
||||
instanceServices: [...serviceMap.keys()].filter((name) =>
|
||||
name.startsWith("hwlab-box-simu-") || name.startsWith("hwlab-gateway-simu-")
|
||||
|
||||
@@ -144,9 +144,11 @@ function validateSource(ctx, manifest) {
|
||||
const cloudApi = servicesById.get("hwlab-cloud-api");
|
||||
const tunnelClient = servicesById.get("hwlab-tunnel-client");
|
||||
const cli = servicesById.get("hwlab-cli");
|
||||
const patchPanel = servicesById.get("hwlab-patch-panel");
|
||||
expectEqual(ctx, cloudApi?.env?.HWLAB_PUBLIC_ENDPOINT, endpoints.api?.url, "$.services.hwlab-cloud-api.env.HWLAB_PUBLIC_ENDPOINT", "cloud API public endpoint env");
|
||||
validateCloudApiDbSource(ctx, cloudApi?.env ?? {});
|
||||
validateCloudApiCodeAgentSource(ctx, cloudApi?.env ?? {});
|
||||
validateM3PatchPanelSource(ctx, patchPanel);
|
||||
expectEqual(ctx, cli?.env?.HWLAB_CLI_ENDPOINT, endpoints.api?.url, "$.services.hwlab-cli.env.HWLAB_CLI_ENDPOINT", "CLI endpoint env");
|
||||
expectEqual(ctx, tunnelClient?.env?.HWLAB_FRP_PUBLIC_PORT, String(expectedPorts.api), "$.services.hwlab-tunnel-client.env.HWLAB_FRP_PUBLIC_PORT", "tunnel API public port env");
|
||||
expectEqual(ctx, tunnelClient?.env?.HWLAB_FRP_WEB_PUBLIC_PORT, String(expectedPorts.frontend), "$.services.hwlab-tunnel-client.env.HWLAB_FRP_WEB_PUBLIC_PORT", "tunnel frontend public port env");
|
||||
@@ -189,6 +191,78 @@ function validateSource(ctx, manifest) {
|
||||
return { manifest, endpoints, servicesById, proxies, serviceMappings, healthPath: manifest.health?.path };
|
||||
}
|
||||
|
||||
function requiredM3Route() {
|
||||
return {
|
||||
fromResourceId: "res_boxsimu_1",
|
||||
fromPort: "DO1",
|
||||
patchPanelServiceId: "hwlab-patch-panel",
|
||||
toResourceId: "res_boxsimu_2",
|
||||
toPort: "DI1"
|
||||
};
|
||||
}
|
||||
|
||||
function parseJsonEnv(ctx, value, jsonPath, label) {
|
||||
try {
|
||||
return JSON.parse(String(value ?? ""));
|
||||
} catch (error) {
|
||||
addDiagnostic(ctx, "invalid_json", jsonPath, `${label} must be JSON`, {
|
||||
actual: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function validateM3PatchPanelSource(ctx, patchPanel) {
|
||||
const route = requiredM3Route();
|
||||
expectEqual(ctx, patchPanel?.m3Route?.fromResourceId, route.fromResourceId, "$.services.hwlab-patch-panel.m3Route.fromResourceId", "patch panel first-class M3 source resource");
|
||||
expectEqual(ctx, patchPanel?.m3Route?.fromPort, route.fromPort, "$.services.hwlab-patch-panel.m3Route.fromPort", "patch panel first-class M3 source port");
|
||||
expectEqual(ctx, patchPanel?.m3Route?.patchPanelServiceId, route.patchPanelServiceId, "$.services.hwlab-patch-panel.m3Route.patchPanelServiceId", "patch panel first-class M3 route owner");
|
||||
expectEqual(ctx, patchPanel?.m3Route?.toResourceId, route.toResourceId, "$.services.hwlab-patch-panel.m3Route.toResourceId", "patch panel first-class M3 target resource");
|
||||
expectEqual(ctx, patchPanel?.m3Route?.toPort, route.toPort, "$.services.hwlab-patch-panel.m3Route.toPort", "patch panel first-class M3 target port");
|
||||
|
||||
const endpointMap = parseJsonEnv(
|
||||
ctx,
|
||||
patchPanel?.env?.HWLAB_PATCH_PANEL_ENDPOINT_MAP,
|
||||
"$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_ENDPOINT_MAP",
|
||||
"patch panel endpoint map"
|
||||
);
|
||||
expectEqual(
|
||||
ctx,
|
||||
endpointMap?.res_boxsimu_2,
|
||||
"http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201",
|
||||
"$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_ENDPOINT_MAP.res_boxsimu_2",
|
||||
"patch panel M3 target endpoint"
|
||||
);
|
||||
|
||||
const wiring = parseJsonEnv(
|
||||
ctx,
|
||||
patchPanel?.env?.HWLAB_PATCH_PANEL_WIRING_CONFIG,
|
||||
"$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG",
|
||||
"patch panel wiring config"
|
||||
);
|
||||
const connection = wiring?.connections?.[0];
|
||||
expectEqual(ctx, wiring?.status, "active", "$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG.status", "patch panel M3 wiring status");
|
||||
expectEqual(
|
||||
ctx,
|
||||
wiring?.constraints?.propagation,
|
||||
"patch-panel-only",
|
||||
"$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG.constraints.propagation",
|
||||
"patch panel M3 wiring propagation"
|
||||
);
|
||||
expectEqual(
|
||||
ctx,
|
||||
wiring?.constraints?.localLoopbackSubstituteAllowed,
|
||||
false,
|
||||
"$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG.constraints.localLoopbackSubstituteAllowed",
|
||||
"patch panel M3 wiring forbids loopback substitute"
|
||||
);
|
||||
expectEqual(ctx, connection?.from?.resourceId, route.fromResourceId, "$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG.connections[0].from.resourceId", "patch panel M3 source resource");
|
||||
expectEqual(ctx, connection?.from?.port, route.fromPort, "$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG.connections[0].from.port", "patch panel M3 source port");
|
||||
expectEqual(ctx, connection?.to?.resourceId, route.toResourceId, "$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG.connections[0].to.resourceId", "patch panel M3 target resource");
|
||||
expectEqual(ctx, connection?.to?.port, route.toPort, "$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG.connections[0].to.port", "patch panel M3 target port");
|
||||
expectEqual(ctx, connection?.mode, "exclusive", "$.services.hwlab-patch-panel.env.HWLAB_PATCH_PANEL_WIRING_CONFIG.connections[0].mode", "patch panel M3 route mode");
|
||||
}
|
||||
|
||||
function validateCloudApiDbSource(ctx, env) {
|
||||
const alias = DEV_DB_ENV_CONTRACT.dns;
|
||||
expectEqual(ctx, env.HWLAB_CLOUD_DB_URL, "secretRef:hwlab-cloud-api-dev-db/database-url", "$.services.hwlab-cloud-api.env.HWLAB_CLOUD_DB_URL", "cloud API DB URL Secret reference");
|
||||
|
||||
@@ -155,7 +155,9 @@ assert.equal(deployServicesById.has("hwlab-agent-worker"), true);
|
||||
assert.equal(deployServicesById.has("hwlab-agent-skills"), true);
|
||||
|
||||
const cloudApi = deployServicesById.get("hwlab-cloud-api");
|
||||
const patchPanel = deployServicesById.get("hwlab-patch-panel");
|
||||
const cloudApiWorkloadEnv = getWorkloadEnv(k8sWorkloads, "hwlab-cloud-api");
|
||||
const patchPanelWorkloadEnv = getWorkloadEnv(k8sWorkloads, "hwlab-patch-panel");
|
||||
assert.equal(deployManifest.health.path, "/health/live", "deploy health source path");
|
||||
assert.equal(deployManifest.publicEndpoints.frontend.url, "http://74.48.78.17:16666", "deploy frontend endpoint");
|
||||
assert.equal(deployManifest.publicEndpoints.api.url, "http://74.48.78.17:16667", "deploy api endpoint");
|
||||
@@ -208,6 +210,7 @@ assert.notEqual(
|
||||
);
|
||||
assert.equal(cloudApi.env.OPENAI_API_KEY, codeAgentSecretRefPlaceholder(), "cloud-api Code Agent OpenAI key must be a Secret reference placeholder");
|
||||
assertCodeAgentProviderWorkloadContract(cloudApiWorkloadEnv);
|
||||
assertM3PatchPanelDeployContract(patchPanel, patchPanelWorkloadEnv);
|
||||
assertDbForbiddenInvalidHostContract();
|
||||
assertValidationDoesNotExposeSecretValues(cloudApi.env, cloudApiWorkloadEnv);
|
||||
|
||||
@@ -262,6 +265,49 @@ function assertCodeAgentProviderWorkloadContract(env) {
|
||||
assert.equal(apiKey?.key, secretRef.secretKey, "cloud-api workload Code Agent OpenAI Secret key");
|
||||
}
|
||||
|
||||
function assertM3PatchPanelDeployContract(manifestService, workloadEnv) {
|
||||
const route = {
|
||||
fromResourceId: "res_boxsimu_1",
|
||||
fromPort: "DO1",
|
||||
patchPanelServiceId: "hwlab-patch-panel",
|
||||
toResourceId: "res_boxsimu_2",
|
||||
toPort: "DI1"
|
||||
};
|
||||
assert.deepEqual(manifestService.m3Route, route, "patch-panel deploy manifest first-class M3 route");
|
||||
const manifestEndpointMap = JSON.parse(manifestService.env.HWLAB_PATCH_PANEL_ENDPOINT_MAP);
|
||||
const workloadEndpointMap = JSON.parse(workloadEnv.HWLAB_PATCH_PANEL_ENDPOINT_MAP?.value ?? "{}");
|
||||
assert.equal(
|
||||
manifestEndpointMap.res_boxsimu_2,
|
||||
"http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201",
|
||||
"patch-panel deploy manifest M3 endpoint"
|
||||
);
|
||||
assert.deepEqual(workloadEndpointMap, manifestEndpointMap, "patch-panel workload M3 endpoint map");
|
||||
|
||||
const manifestWiring = JSON.parse(manifestService.env.HWLAB_PATCH_PANEL_WIRING_CONFIG);
|
||||
const workloadWiring = JSON.parse(workloadEnv.HWLAB_PATCH_PANEL_WIRING_CONFIG?.value ?? "{}");
|
||||
assert.deepEqual(workloadWiring, manifestWiring, "patch-panel workload first-class M3 wiring config");
|
||||
assert.equal(manifestWiring.status, "active", "patch-panel M3 wiring status");
|
||||
assert.equal(manifestWiring.constraints.propagation, "patch-panel-only", "patch-panel M3 wiring propagation");
|
||||
assert.equal(
|
||||
manifestWiring.constraints.localLoopbackSubstituteAllowed,
|
||||
false,
|
||||
"patch-panel M3 wiring loopback substitute"
|
||||
);
|
||||
assert.deepEqual(manifestWiring.connections, [
|
||||
{
|
||||
from: {
|
||||
resourceId: route.fromResourceId,
|
||||
port: route.fromPort
|
||||
},
|
||||
to: {
|
||||
resourceId: route.toResourceId,
|
||||
port: route.toPort
|
||||
},
|
||||
mode: "exclusive"
|
||||
}
|
||||
], "patch-panel M3 wiring route");
|
||||
}
|
||||
|
||||
function assertDbForbiddenInvalidHostContract() {
|
||||
const invalidHost = parseDbUrlContract("postgres://user:password@hwlab-dev-db.invalid:5432/hwlab");
|
||||
assert.equal(invalidHost.ok, false, "DEV DB contract must reject .invalid runtime hosts");
|
||||
|
||||
@@ -9,6 +9,32 @@ const namespace = "hwlab-dev";
|
||||
const m3EndpointMap = Object.freeze({
|
||||
res_boxsimu_2: "http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201"
|
||||
});
|
||||
const m3WiringConfig = Object.freeze({
|
||||
wiringConfigId: "wir_m3_do1_di1",
|
||||
projectId: "prj_m3_hardware_loop",
|
||||
gatewaySessionId: "gws_gwsimu_1",
|
||||
name: "M3 simulated DO1 to DI1 patch wiring",
|
||||
status: "active",
|
||||
connections: [
|
||||
{
|
||||
from: {
|
||||
resourceId: "res_boxsimu_1",
|
||||
port: "DO1"
|
||||
},
|
||||
to: {
|
||||
resourceId: "res_boxsimu_2",
|
||||
port: "DI1"
|
||||
},
|
||||
mode: "exclusive"
|
||||
}
|
||||
],
|
||||
constraints: {
|
||||
propagation: "patch-panel-only",
|
||||
localLoopbackSubstituteAllowed: false
|
||||
},
|
||||
createdAt: "2026-05-21T00:00:00.000Z",
|
||||
updatedAt: "2026-05-21T00:00:00.000Z"
|
||||
});
|
||||
const expected = Object.freeze({
|
||||
"hwlab-box-simu": {
|
||||
kind: "StatefulSet",
|
||||
@@ -39,8 +65,16 @@ const expected = Object.freeze({
|
||||
"hwlab-patch-panel": {
|
||||
kind: "Deployment",
|
||||
replicas: 1,
|
||||
m3Route: {
|
||||
fromResourceId: "res_boxsimu_1",
|
||||
fromPort: "DO1",
|
||||
patchPanelServiceId: "hwlab-patch-panel",
|
||||
toResourceId: "res_boxsimu_2",
|
||||
toPort: "DI1"
|
||||
},
|
||||
env: {
|
||||
HWLAB_PATCH_PANEL_ENDPOINT_MAP: m3EndpointMap
|
||||
HWLAB_PATCH_PANEL_ENDPOINT_MAP: m3EndpointMap,
|
||||
HWLAB_PATCH_PANEL_WIRING_CONFIG: m3WiringConfig
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -197,6 +231,9 @@ function assertDeployManifestCardinality(deploy) {
|
||||
assertSameMembers(service.instanceNames ?? [], requirement.instanceNames, `${serviceId} deploy instanceNames`);
|
||||
assert.equal(service.identityStrategy, "indexed-workloads", `${serviceId} deploy identityStrategy`);
|
||||
}
|
||||
if (requirement.m3Route) {
|
||||
assert.deepEqual(service.m3Route, requirement.m3Route, `${serviceId} deploy first-class M3 route`);
|
||||
}
|
||||
|
||||
for (const [name, values] of Object.entries(requirement.env)) {
|
||||
if (Array.isArray(values)) {
|
||||
@@ -245,6 +282,25 @@ function assertM3FixtureWiring(topology) {
|
||||
], "M3 fixture wiring config");
|
||||
}
|
||||
|
||||
function assertM3RouteConfig(value, label) {
|
||||
assert.deepEqual(value, m3WiringConfig, label);
|
||||
assert.deepEqual(value.connections, [
|
||||
{
|
||||
from: {
|
||||
resourceId: "res_boxsimu_1",
|
||||
port: "DO1"
|
||||
},
|
||||
to: {
|
||||
resourceId: "res_boxsimu_2",
|
||||
port: "DI1"
|
||||
},
|
||||
mode: "exclusive"
|
||||
}
|
||||
], `${label} required route`);
|
||||
assert.equal(value.constraints.propagation, "patch-panel-only", `${label} propagation`);
|
||||
assert.equal(value.constraints.localLoopbackSubstituteAllowed, false, `${label} loopback substitute`);
|
||||
}
|
||||
|
||||
const [deploy, workloads, services, topology] = await Promise.all([
|
||||
readJson("deploy/deploy.json"),
|
||||
readJson("deploy/k8s/base/workloads.yaml"),
|
||||
@@ -252,10 +308,25 @@ const [deploy, workloads, services, topology] = await Promise.all([
|
||||
readJson("fixtures/mvp/m3-hardware-loop/topology.json")
|
||||
]);
|
||||
|
||||
const patchPanelWorkloadEnv = envMap(
|
||||
workloadByName(workloads)
|
||||
.get("hwlab-patch-panel")
|
||||
?.spec?.template?.spec?.containers?.find((item) => item.name === "hwlab-patch-panel")
|
||||
?.env
|
||||
);
|
||||
|
||||
assertDeployManifestCardinality(deploy);
|
||||
assertDeployManifestInstanceMappings(deploy);
|
||||
assertDevK8sCardinality(workloads);
|
||||
assertDevK8sInstanceServices(services);
|
||||
assertM3FixtureWiring(topology);
|
||||
assertM3RouteConfig(
|
||||
parseEnvValue(patchPanelWorkloadEnv.HWLAB_PATCH_PANEL_WIRING_CONFIG),
|
||||
"DEV workload first-class M3 patch-panel wiring config"
|
||||
);
|
||||
assertM3RouteConfig(
|
||||
parseEnvValue(deploy.services.find((service) => service.serviceId === "hwlab-patch-panel")?.env?.HWLAB_PATCH_PANEL_WIRING_CONFIG),
|
||||
"deploy manifest first-class M3 patch-panel wiring config"
|
||||
);
|
||||
|
||||
console.log("validated DEV M3 manifest cardinality: 2 distinct box-simu, 2 distinct gateway-simu, 1 patch-panel, M3 DO1->DI1 wiring");
|
||||
|
||||
Reference in New Issue
Block a user