fix: wire distinct M3 simulator identities
Merge PR #159 after Code Queue rebase and validation. Runner verified M3 cardinality, hardware-loop smoke, patch-panel runtime smoke, m3 readonly contract, and git diff check. This is source/config blocker removal only; it does not claim DEV-LIVE M3 pass.
This commit is contained in:
@@ -17,6 +17,7 @@ const defaultReportPath = "reports/dev-gate/dev-m3-hardware-loop.json";
|
||||
const namespace = "hwlab-dev";
|
||||
const issue = "pikasTech/HWLAB#38";
|
||||
const deployWorkloadsPath = "deploy/k8s/base/workloads.yaml";
|
||||
const deployServicesPath = "deploy/k8s/base/services.yaml";
|
||||
const requiredManifestCardinalityCommand = "node scripts/validate-dev-m3-cardinality.mjs";
|
||||
const requiredM3BoxIds = Object.freeze(["boxsimu_1", "boxsimu_2"]);
|
||||
const requiredM3BoxResources = Object.freeze(["res_boxsimu_1", "res_boxsimu_2"]);
|
||||
@@ -26,6 +27,7 @@ const requiredM3Connection = Object.freeze({
|
||||
toResourceId: "res_boxsimu_2",
|
||||
toPort: "DI1"
|
||||
});
|
||||
const requiredM3GatewayIds = Object.freeze(["gwsimu_1", "gwsimu_2"]);
|
||||
const runnerK3sKubeconfigPath = "/etc/rancher/k3s/k3s.yaml";
|
||||
|
||||
const serviceTargets = Object.freeze([
|
||||
@@ -285,11 +287,25 @@ function hasSameMembers(actual, expected) {
|
||||
return actual.length === expected.length && expected.every((item) => actual.includes(item));
|
||||
}
|
||||
|
||||
function listItems(document) {
|
||||
return document?.kind === "List" ? document.items ?? [] : [document].filter(Boolean);
|
||||
}
|
||||
|
||||
function serviceByName(services) {
|
||||
return new Map(
|
||||
listItems(services)
|
||||
.filter((item) => item?.kind === "Service")
|
||||
.map((item) => [item.metadata?.name, item])
|
||||
);
|
||||
}
|
||||
|
||||
async function collectReadOnlySupplementalEvidence() {
|
||||
const raw = await readFile(path.join(repoRoot, deployWorkloadsPath), "utf8");
|
||||
const workloads = JSON.parse(raw);
|
||||
const [workloads, services] = await Promise.all([
|
||||
readFile(path.join(repoRoot, deployWorkloadsPath), "utf8").then(JSON.parse),
|
||||
readFile(path.join(repoRoot, deployServicesPath), "utf8").then(JSON.parse)
|
||||
]);
|
||||
const targets = ["hwlab-box-simu", "hwlab-gateway-simu", "hwlab-patch-panel"];
|
||||
const observed = workloads.items
|
||||
const observed = listItems(workloads)
|
||||
.filter((item) => targets.includes(item.metadata?.name))
|
||||
.map((item) => {
|
||||
const container = item.spec?.template?.spec?.containers?.[0] ?? {};
|
||||
@@ -308,28 +324,56 @@ async function collectReadOnlySupplementalEvidence() {
|
||||
const gateway = observed.find((item) => item.name === "hwlab-gateway-simu");
|
||||
const patchPanel = observed.find((item) => item.name === "hwlab-patch-panel");
|
||||
const boxIds = splitList(box?.env.HWLAB_BOX_ID);
|
||||
const boxResourceIds = splitList(box?.env.HWLAB_BOX_RESOURCE_ID);
|
||||
const boxGatewaySessionIds = splitList(box?.env.HWLAB_GATEWAY_SESSION_ID);
|
||||
const gatewayIds = splitList(gateway?.env.HWLAB_GATEWAY_ID);
|
||||
const gatewayBoxResources = splitList(gateway?.env.HWLAB_BOX_RESOURCES);
|
||||
const boxReadyForM3 = box?.replicas === 2 && hasSameMembers(boxIds, requiredM3BoxIds);
|
||||
const gatewayBoxUrls = splitList(gateway?.env.HWLAB_BOX_URLS);
|
||||
const serviceMap = serviceByName(services);
|
||||
const hasInstanceServices = [
|
||||
"hwlab-box-simu-1",
|
||||
"hwlab-box-simu-2",
|
||||
"hwlab-gateway-simu-1",
|
||||
"hwlab-gateway-simu-2"
|
||||
].every((name) => serviceMap.has(name));
|
||||
const patchPanelEndpointMap = patchPanel?.env.HWLAB_PATCH_PANEL_ENDPOINT_MAP
|
||||
? JSON.parse(patchPanel.env.HWLAB_PATCH_PANEL_ENDPOINT_MAP)
|
||||
: {};
|
||||
const boxReadyForM3 =
|
||||
box?.kind === "StatefulSet" &&
|
||||
box?.replicas === 2 &&
|
||||
hasSameMembers(boxIds, requiredM3BoxIds) &&
|
||||
hasSameMembers(boxResourceIds, requiredM3BoxResources) &&
|
||||
hasSameMembers(boxGatewaySessionIds, ["gws_gwsimu_1", "gws_gwsimu_2"]);
|
||||
const gatewayReadyForM3 =
|
||||
gateway?.replicas === 2 && hasSameMembers(gatewayBoxResources, requiredM3BoxResources);
|
||||
const patchPanelReadyForM3 = patchPanel?.replicas === 1;
|
||||
const manifestReadyForM3 = boxReadyForM3 && gatewayReadyForM3 && patchPanelReadyForM3;
|
||||
gateway?.kind === "StatefulSet" &&
|
||||
gateway?.replicas === 2 &&
|
||||
hasSameMembers(gatewayIds, requiredM3GatewayIds) &&
|
||||
hasSameMembers(gatewayBoxResources, requiredM3BoxResources) &&
|
||||
hasSameMembers(gatewayBoxUrls, [
|
||||
"http://hwlab-box-simu-1.hwlab-dev.svc.cluster.local:7201",
|
||||
"http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201"
|
||||
]);
|
||||
const patchPanelReadyForM3 =
|
||||
patchPanel?.replicas === 1 &&
|
||||
patchPanelEndpointMap.res_boxsimu_2 === "http://hwlab-box-simu-2.hwlab-dev.svc.cluster.local:7201";
|
||||
const manifestReadyForM3 = boxReadyForM3 && gatewayReadyForM3 && patchPanelReadyForM3 && hasInstanceServices;
|
||||
|
||||
return [
|
||||
{
|
||||
id: "deploy-skeleton-m3-cardinality",
|
||||
status: manifestReadyForM3 ? "manifest-ready" : "gap",
|
||||
blockerClass: manifestReadyForM3 ? null : "runtime_blocker",
|
||||
source: deployWorkloadsPath,
|
||||
source: [deployWorkloadsPath, deployServicesPath],
|
||||
summary: manifestReadyForM3
|
||||
? "Checked-in DEV deploy skeleton declares M3 cardinality: two box simulators, two gateway simulators, and one patch panel."
|
||||
: "Read-only static comparison found the checked-in DEV deploy skeleton does not declare the required M3 simulator and patch-panel cardinality; proving two of each simulator in live DEV requires a separate deploy/runtime task or authorized runtime observation.",
|
||||
? "Checked-in DEV deploy skeleton declares distinct indexed M3 identities for two box simulators, two gateway simulators, one patch panel, and the M3 DO1 -> DI1 endpoint map."
|
||||
: "Read-only static comparison found the checked-in DEV deploy skeleton does not declare distinct indexed M3 simulator identities and patch-panel M3 wiring; proving live DEV still requires a separate deploy/runtime task or authorized runtime observation.",
|
||||
evidence: {
|
||||
required: {
|
||||
"hwlab-box-simu": 2,
|
||||
"hwlab-gateway-simu": 2,
|
||||
"hwlab-patch-panel": 1,
|
||||
wiring: "box-simu-1 DO1 -> box-simu-2 DI1"
|
||||
wiring: "res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1"
|
||||
},
|
||||
observed: {
|
||||
"hwlab-box-simu": {
|
||||
@@ -343,7 +387,10 @@ async function collectReadOnlySupplementalEvidence() {
|
||||
"hwlab-patch-panel": {
|
||||
replicas: patchPanel?.replicas ?? null,
|
||||
env: patchPanel?.env ?? null
|
||||
}
|
||||
},
|
||||
instanceServices: [...serviceMap.keys()].filter((name) =>
|
||||
name.startsWith("hwlab-box-simu-") || name.startsWith("hwlab-gateway-simu-")
|
||||
)
|
||||
}
|
||||
},
|
||||
validationCommand: requiredManifestCardinalityCommand,
|
||||
|
||||
Reference in New Issue
Block a user