fix: persist code agent workspace

This commit is contained in:
Codex
2026-05-27 13:25:52 +08:00
parent 3c7f13191b
commit d02b7cd8a6
8 changed files with 421 additions and 16 deletions
+115 -10
View File
@@ -42,6 +42,11 @@ async function makeFixture({
workloadCodeAgentProviderEnv = true,
workloadCodexHomeVolumeMounts = true,
workloadCodexHomeVolumes = true,
workloadWorkspaceVolumeMount = true,
workloadWorkspaceVolume = true,
workloadWorkspaceInitContainer = true,
workloadWorkspaceRecreateStrategy = true,
workloadWorkspacePvc = true,
workloadDbEnv = true,
workloadDbSslMode = "disable"
} = {}) {
@@ -65,6 +70,8 @@ async function makeFixture({
HWLAB_CODE_AGENT_CODEX_API_UPSTREAM_BASE_URL: "https://hyueapi.com",
HWLAB_CODE_AGENT_CODEX_API_FORWARDER_PORT: "49280",
HWLAB_CODE_AGENT_OPENAI_BASE_URL: providerBaseUrl,
HWLAB_CODE_AGENT_WORKSPACE: "/workspace/hwlab",
HWLAB_CODE_AGENT_CODEX_WORKSPACE: "/workspace/hwlab",
CODEX_HOME: "/codex-home",
NO_PROXY: providerNoProxy,
no_proxy: providerNoProxy,
@@ -84,6 +91,8 @@ async function makeFixture({
{ name: "HWLAB_CODE_AGENT_CODEX_API_UPSTREAM_BASE_URL", value: "https://hyueapi.com" },
{ name: "HWLAB_CODE_AGENT_CODEX_API_FORWARDER_PORT", value: "49280" },
{ name: "HWLAB_CODE_AGENT_OPENAI_BASE_URL", value: providerBaseUrl },
{ name: "HWLAB_CODE_AGENT_WORKSPACE", value: "/workspace/hwlab" },
{ name: "HWLAB_CODE_AGENT_CODEX_WORKSPACE", value: "/workspace/hwlab" },
{ name: "CODEX_HOME", value: "/codex-home" },
{ name: "NO_PROXY", value: providerNoProxy },
{ name: "no_proxy", value: providerNoProxy },
@@ -175,6 +184,29 @@ async function makeFixture({
apiVersion: "v1",
kind: "List",
items: [
...(serviceId === "hwlab-cloud-api" && workloadCodeAgentProviderEnv && workloadWorkspacePvc
? [
{
apiVersion: "v1",
kind: "PersistentVolumeClaim",
metadata: {
name: "hwlab-code-agent-workspace",
namespace: "hwlab-dev",
labels: {
"hwlab.pikastech.local/service-id": "hwlab-code-agent-workspace"
}
},
spec: {
accessModes: ["ReadWriteOnce"],
resources: {
requests: {
storage: "8Gi"
}
}
}
}
]
: []),
{
apiVersion: "apps/v1",
kind: "Deployment",
@@ -186,8 +218,33 @@ async function makeFixture({
}
},
spec: {
...(serviceId === "hwlab-cloud-api" && workloadCodeAgentProviderEnv && workloadWorkspaceRecreateStrategy
? {
strategy: {
type: "Recreate"
}
}
: {}),
template: {
spec: {
...(serviceId === "hwlab-cloud-api" && workloadCodeAgentProviderEnv && workloadWorkspaceInitContainer
? {
initContainers: [
{
name: "hwlab-code-agent-workspace-init",
image: workloadImage,
command: [
"sh",
"-ceu",
"target=/workspace/hwlab\nmarker=\"$target/.hwlab-workspace-initialized\"\nmkdir -p \"$target\"\nif [ ! -e \"$marker\" ]; then\n cp -a /app/. \"$target\"/\n printf '%s\\n' copied-from-image > \"$marker\"\nfi\n"
],
volumeMounts: [
{ name: "hwlab-code-agent-workspace", mountPath: "/workspace" }
]
}
]
}
: {}),
containers: [
{
name: serviceId,
@@ -202,12 +259,19 @@ async function makeFixture({
...workloadProviderEnv
]
: [],
...(serviceId === "hwlab-cloud-api" && workloadCodeAgentProviderEnv && workloadCodexHomeVolumeMounts
...(serviceId === "hwlab-cloud-api" && workloadCodeAgentProviderEnv && (workloadWorkspaceVolumeMount || workloadCodexHomeVolumeMounts)
? {
volumeMounts: [
{ name: "hwlab-code-agent-codex-home", mountPath: "/codex-home" },
{ name: "hwlab-code-agent-codex-config", mountPath: "/codex-home/config.toml", subPath: "config.toml", readOnly: true },
{ name: "hwlab-code-agent-codex-auth", mountPath: "/codex-home/auth.json", subPath: "auth.json", readOnly: true }
...(workloadWorkspaceVolumeMount
? [{ name: "hwlab-code-agent-workspace", mountPath: "/workspace" }]
: []),
...(workloadCodexHomeVolumeMounts
? [
{ name: "hwlab-code-agent-codex-home", mountPath: "/codex-home" },
{ name: "hwlab-code-agent-codex-config", mountPath: "/codex-home/config.toml", subPath: "config.toml", readOnly: true },
{ name: "hwlab-code-agent-codex-auth", mountPath: "/codex-home/auth.json", subPath: "auth.json", readOnly: true }
]
: [])
]
}
: {})
@@ -230,12 +294,19 @@ async function makeFixture({
]
: [])
],
...(serviceId === "hwlab-cloud-api" && workloadCodeAgentProviderEnv && workloadCodexHomeVolumes
...(serviceId === "hwlab-cloud-api" && workloadCodeAgentProviderEnv && (workloadWorkspaceVolume || workloadCodexHomeVolumes)
? {
volumes: [
{ name: "hwlab-code-agent-codex-home", emptyDir: {} },
{ name: "hwlab-code-agent-codex-config", configMap: { name: "hwlab-code-agent-codex-config", items: [{ key: "config.toml", path: "config.toml" }] } },
{ name: "hwlab-code-agent-codex-auth", secret: { secretName: "hwlab-code-agent-codex-auth", items: [{ key: "auth.json", path: "auth.json" }] } }
...(workloadWorkspaceVolume
? [{ name: "hwlab-code-agent-workspace", persistentVolumeClaim: { claimName: "hwlab-code-agent-workspace" } }]
: []),
...(workloadCodexHomeVolumes
? [
{ name: "hwlab-code-agent-codex-home", emptyDir: {} },
{ name: "hwlab-code-agent-codex-config", configMap: { name: "hwlab-code-agent-codex-config", items: [{ key: "config.toml", path: "config.toml" }] } },
{ name: "hwlab-code-agent-codex-auth", secret: { secretName: "hwlab-code-agent-codex-auth", items: [{ key: "auth.json", path: "auth.json" }] } }
]
: [])
]
}
: {})
@@ -307,6 +378,11 @@ test("passes when cloud-api preserves provider env and DEV egress contract", asy
assert.equal(plan.codeAgentProvider.codexHome.emptyDirPresent, true);
assert.equal(plan.codeAgentProvider.codexHome.configMapPresent, true);
assert.equal(plan.codeAgentProvider.codexHome.authSecretPresent, true);
assert.equal(plan.codeAgentProvider.workspace.present, true);
assert.equal(plan.codeAgentProvider.workspace.writableMountPresent, true);
assert.equal(plan.codeAgentProvider.workspace.persistentVolumeClaimPresent, true);
assert.equal(plan.codeAgentProvider.workspace.initContainerPresent, true);
assert.equal(plan.codeAgentProvider.workspace.recreateStrategy, true);
assert.deepEqual(plan.diagnostics, []);
});
@@ -324,11 +400,39 @@ test("blocks when cloud-api CODEX_HOME mount is missing its writable emptyDir vo
assert.ok(diagnostic.actual.missingCodexHomeContract.includes("/codex-home must be backed by emptyDir"));
});
test("blocks when cloud-api Code Agent workspace is not backed by PVC", async () => {
const repoRoot = await makeFixture({ workloadWorkspaceVolume: false });
const plan = await buildDesiredStatePlan({ repoRoot });
const diagnostic = plan.diagnostics.find((entry) => entry.code === "code_agent_provider_contract_mismatch");
assert.equal(plan.status, "blocked");
assert.equal(plan.codeAgentProvider.ready, false);
assert.equal(plan.codeAgentProvider.workspace.present, false);
assert.equal(plan.codeAgentProvider.workspace.writableMountPresent, true);
assert.equal(plan.codeAgentProvider.workspace.persistentVolumeClaimPresent, false);
assert.ok(diagnostic);
assert.ok(diagnostic.actual.missingWorkspaceContract.includes("hwlab-code-agent-workspace must use PVC hwlab-code-agent-workspace"));
});
test("blocks when cloud-api Code Agent workspace rollout strategy is not Recreate", async () => {
const repoRoot = await makeFixture({ workloadWorkspaceRecreateStrategy: false });
const plan = await buildDesiredStatePlan({ repoRoot });
const diagnostic = plan.diagnostics.find((entry) => entry.code === "code_agent_provider_contract_mismatch");
assert.equal(plan.status, "blocked");
assert.equal(plan.codeAgentProvider.ready, false);
assert.equal(plan.codeAgentProvider.workspace.present, false);
assert.equal(plan.codeAgentProvider.workspace.recreateStrategy, false);
assert.ok(diagnostic);
assert.ok(diagnostic.actual.missingWorkspaceContract.includes("hwlab-cloud-api rollout strategy must be Recreate"));
});
test("blocks when cloud-api CODEX_HOME mount is read-only", async () => {
const repoRoot = await makeFixture();
const workloadsPath = path.join(repoRoot, "deploy/k8s/base/workloads.yaml");
const workloads = JSON.parse(await readFile(workloadsPath, "utf8"));
workloads.items[0].spec.template.spec.containers[0].volumeMounts.find((entry) =>
const deployment = workloads.items.find((entry) => entry.kind === "Deployment");
deployment.spec.template.spec.containers[0].volumeMounts.find((entry) =>
entry.name === "hwlab-code-agent-codex-home"
).readOnly = true;
await writeFile(workloadsPath, `${JSON.stringify(workloads, null, 2)}\n`);
@@ -391,7 +495,8 @@ test("blocks when cloud-api workload points Code Agent directly at public OpenAI
const repoRoot = await makeFixture();
const workloadsPath = path.join(repoRoot, "deploy/k8s/base/workloads.yaml");
const workloads = JSON.parse(await readFile(workloadsPath, "utf8"));
const env = workloads.items[0].spec.template.spec.containers[0].env;
const deployment = workloads.items.find((entry) => entry.kind === "Deployment");
const env = deployment.spec.template.spec.containers[0].env;
env.find((entry) => entry.name === "HWLAB_CODE_AGENT_OPENAI_BASE_URL").value = "https://api.openai.com/v1/responses";
await writeFile(workloadsPath, `${JSON.stringify(workloads, null, 2)}\n`);
+14
View File
@@ -293,6 +293,19 @@ function upsertEnvEntry(envList, entry) {
}
}
function syncCodeAgentWorkspaceInitContainer(initContainers, { image, runtimeCommit, runtimeImageTag }) {
const initContainer = Array.isArray(initContainers)
? initContainers.find((entry) => entry?.name === "hwlab-code-agent-workspace-init")
: null;
if (!initContainer) return;
initContainer.image = image;
initContainer.imagePullPolicy = "IfNotPresent";
initContainer.env ??= [];
upsertEnv(initContainer.env, "HWLAB_WORKSPACE_SOURCE_COMMIT", runtimeCommit);
upsertEnv(initContainer.env, "HWLAB_IMAGE", image);
upsertEnv(initContainer.env, "HWLAB_IMAGE_TAG", runtimeImageTag);
}
function annotate(metadata, annotations) {
metadata.annotations = { ...(metadata.annotations ?? {}), ...annotations };
}
@@ -502,6 +515,7 @@ function transformWorkloads({ workloads, deploy, catalog, source, registryPrefix
upsertEnv(container.env, "HWLAB_PUBLIC_ENDPOINT", runtimeEndpoint);
}
if (serviceId === "hwlab-cloud-api") {
syncCodeAgentWorkspaceInitContainer(podTemplate.spec.initContainers, { image, runtimeCommit, runtimeImageTag });
upsertEnv(container.env, "HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE", "deepseek");
upsertEnv(container.env, "HWLAB_CODE_AGENT_DEEPSEEK_MODEL", deepSeekProfileModel);
upsertEnv(container.env, "HWLAB_CODE_AGENT_DEEPSEEK_BASE_URL", deepSeekBaseUrl(namespace));
+78 -3
View File
@@ -577,10 +577,15 @@ function validateCloudApiDbOptionalAliasArtifacts(ctx, services, workloads) {
function validateCloudApiCodeAgentArtifacts(ctx, workloads) {
const contract = DEV_CODE_AGENT_PROVIDER_CONTRACT;
const secretRef = contract.secretRefs[0];
const container = mapByServiceId(listItems(workloads)).get("hwlab-cloud-api")?.spec?.template?.spec?.containers?.[0];
const workloadItems = mapByServiceId(listItems(workloads));
const workload = workloadItems.get("hwlab-cloud-api");
const templateSpec = workload?.spec?.template?.spec ?? {};
const container = templateSpec.containers?.[0];
const env = new Map((container?.env ?? []).map((entry) => [entry.name, entry]));
const podContainers = mapByServiceId(listItems(workloads)).get("hwlab-cloud-api")?.spec?.template?.spec?.containers ?? [];
const volumes = mapByServiceId(listItems(workloads)).get("hwlab-cloud-api")?.spec?.template?.spec?.volumes ?? [];
const podContainers = templateSpec.containers ?? [];
const initContainers = templateSpec.initContainers ?? [];
const volumes = templateSpec.volumes ?? [];
const workspacePvc = workloadItems.get(contract.workspace.claimName);
expectEqual(ctx, env.get("HWLAB_CODE_AGENT_PROVIDER")?.value, contract.codeAgentProvider, "deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.HWLAB_CODE_AGENT_PROVIDER", "cloud API workload Code Agent provider");
expectEqual(ctx, env.get("HWLAB_CODE_AGENT_MODEL")?.value, contract.model, "deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.HWLAB_CODE_AGENT_MODEL", "cloud API workload Code Agent model");
expectEqual(ctx, env.get("HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE")?.value, contract.profiles.defaultProfile, "deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE", "cloud API workload default provider profile");
@@ -616,6 +621,7 @@ function validateCloudApiCodeAgentArtifacts(ctx, workloads) {
expectNoProxyIncludes(ctx, env.get("no_proxy")?.value, contract.noProxyRequired, "deploy/k8s/base/workloads.yaml.hwlab-cloud-api.env.no_proxy");
validateCodexApiForwarderArtifacts(ctx, podContainers, contract);
validateCodexHomeMountArtifacts(ctx, container?.volumeMounts ?? [], volumes, contract);
validateCodeAgentWorkspaceArtifacts(ctx, container?.volumeMounts ?? [], volumes, initContainers, workload?.spec?.strategy ?? null, workspacePvc, contract);
expect(
ctx,
env.get("HWLAB_CODE_AGENT_OPENAI_BASE_URL")?.value !== contract.egress.forbiddenDirectBaseUrl,
@@ -714,6 +720,75 @@ function validateCodexHomeMountArtifacts(ctx, volumeMounts, volumes, contract) {
);
}
function validateCodeAgentWorkspaceArtifacts(ctx, volumeMounts, volumes, initContainers, strategy, pvc, contract) {
const workspace = contract.workspace;
expect(
ctx,
volumeMounts.some((entry) => entry.name === workspace.volumeName && entry.mountPath === workspace.mountPath && entry.readOnly !== true),
"code_agent_workspace_mount_missing",
"deploy/k8s/base/workloads.yaml.hwlab-cloud-api.volumeMounts.hwlab-code-agent-workspace",
"cloud API workload Code Agent workspace writable mount"
);
expect(
ctx,
volumes.some((entry) => entry.name === workspace.volumeName && entry.persistentVolumeClaim?.claimName === workspace.claimName),
"code_agent_workspace_pvc_volume_missing",
"deploy/k8s/base/workloads.yaml.hwlab-cloud-api.volumes.hwlab-code-agent-workspace",
"cloud API workload Code Agent workspace PVC volume"
);
expect(
ctx,
pvc?.kind === "PersistentVolumeClaim" && pvc?.metadata?.name === workspace.claimName,
"code_agent_workspace_pvc_missing",
"deploy/k8s/base/workloads.yaml.PersistentVolumeClaim.hwlab-code-agent-workspace",
"Code Agent workspace PVC resource"
);
expect(
ctx,
(pvc?.spec?.accessModes ?? []).includes(workspace.accessMode),
"code_agent_workspace_pvc_access_mode_mismatch",
"deploy/k8s/base/workloads.yaml.PersistentVolumeClaim.hwlab-code-agent-workspace.spec.accessModes",
"Code Agent workspace PVC uses ReadWriteOnce"
);
expectEqual(
ctx,
pvc?.spec?.resources?.requests?.storage,
workspace.storage,
"deploy/k8s/base/workloads.yaml.PersistentVolumeClaim.hwlab-code-agent-workspace.spec.resources.requests.storage",
"Code Agent workspace PVC requested storage"
);
const initContainer = initContainers.find((entry) => entry.name === workspace.initContainerName);
expect(
ctx,
Boolean(initContainer),
"code_agent_workspace_init_container_missing",
"deploy/k8s/base/workloads.yaml.hwlab-cloud-api.initContainers.hwlab-code-agent-workspace-init",
"cloud API workload initializes persistent Code Agent workspace"
);
expect(
ctx,
(initContainer?.volumeMounts ?? []).some((entry) => entry.name === workspace.volumeName && entry.mountPath === workspace.mountPath),
"code_agent_workspace_init_mount_missing",
"deploy/k8s/base/workloads.yaml.hwlab-cloud-api.initContainers.hwlab-code-agent-workspace-init.volumeMounts",
"Code Agent workspace init container mounts the persistent workspace"
);
const initCommand = [...(initContainer?.command ?? []), ...(initContainer?.args ?? [])].join("\n");
expect(
ctx,
initCommand.includes(workspace.sourcePath) && initCommand.includes(workspace.path) && initCommand.includes(workspace.initMarker),
"code_agent_workspace_init_command_mismatch",
"deploy/k8s/base/workloads.yaml.hwlab-cloud-api.initContainers.hwlab-code-agent-workspace-init.command",
"Code Agent workspace init container uses copy-once /app bootstrap"
);
expectEqual(
ctx,
strategy?.type,
workspace.rolloutStrategy,
"deploy/k8s/base/workloads.yaml.hwlab-cloud-api.strategy.type",
"cloud API uses Recreate rollout for single-writer workspace PVC"
);
}
function validateCodeAgentProxyTimeoutArtifacts(ctx, workloads) {
const workloadItems = mapByServiceId(listItems(workloads));
const cloudWebEnv = new Map((workloadItems.get("hwlab-cloud-web")?.spec?.template?.spec?.containers?.[0]?.env ?? []).map((entry) => [entry.name, entry.value]));
+4 -1
View File
@@ -305,6 +305,7 @@ function inspectCodeAgentProviderDesiredState(ctx, deployService, workloadRecord
missingSecretRefs: inspection.missingSecretRefs,
missingEgressContract: inspection.missingEgressContract,
missingCodexHomeContract: inspection.missingCodexHomeContract,
missingWorkspaceContract: inspection.missingWorkspaceContract,
missingNoProxyContract: inspection.missingNoProxyContract,
missingForwarderContract: inspection.missingForwarderContract
},
@@ -444,7 +445,9 @@ function indexWorkloads(workloads) {
...envListToObject(container?.env),
__volumeMounts: Array.isArray(container?.volumeMounts) ? container.volumeMounts : [],
__volumes: Array.isArray(item?.spec?.template?.spec?.volumes) ? item.spec.template.spec.volumes : [],
__podContainers: Array.isArray(item?.spec?.template?.spec?.containers) ? item.spec.template.spec.containers : []
__podContainers: Array.isArray(item?.spec?.template?.spec?.containers) ? item.spec.template.spec.containers : [],
__initContainers: Array.isArray(item?.spec?.template?.spec?.initContainers) ? item.spec.template.spec.initContainers : [],
__deploymentStrategy: item?.spec?.strategy ?? null
},
path: `${workloadsPath}.${item?.kind ?? "Workload"}.${item?.metadata?.name ?? serviceId}.containers[${containerIndex}]`
};
+36
View File
@@ -179,6 +179,9 @@ const cloudApiWorkloadEnv = getWorkloadEnv(k8sWorkloads, "hwlab-cloud-api");
cloudApiWorkloadEnv.__volumeMounts = cloudApiContainer?.volumeMounts ?? [];
cloudApiWorkloadEnv.__volumes = cloudApiWorkload?.spec?.template?.spec?.volumes ?? [];
cloudApiWorkloadEnv.__podContainers = cloudApiWorkload?.spec?.template?.spec?.containers ?? [];
cloudApiWorkloadEnv.__initContainers = cloudApiWorkload?.spec?.template?.spec?.initContainers ?? [];
cloudApiWorkloadEnv.__deploymentStrategy = cloudApiWorkload?.spec?.strategy ?? null;
cloudApiWorkloadEnv.__workspacePvc = getWorkload(k8sWorkloads, DEV_CODE_AGENT_PROVIDER_CONTRACT.workspace.claimName);
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");
@@ -324,6 +327,7 @@ function assertCodeAgentProviderWorkloadContract(env) {
assertNoProxyIncludes(env.NO_PROXY?.value, contract.noProxyRequired, "cloud-api workload NO_PROXY contract");
assertNoProxyIncludes(env.no_proxy?.value, contract.noProxyRequired, "cloud-api workload no_proxy contract");
assertCodexHomeMountContract(env, contract);
assertCodeAgentWorkspaceContract(env, contract);
assertCodexApiForwarderContract(env, contract);
assert.notEqual(
env.HWLAB_CODE_AGENT_OPENAI_BASE_URL?.value,
@@ -403,6 +407,38 @@ function assertCodexHomeMountContract(env, contract) {
);
}
function assertCodeAgentWorkspaceContract(env, contract) {
const workspace = contract.workspace;
const volumeMounts = env.__volumeMounts ?? [];
const volumes = env.__volumes ?? [];
const initContainers = env.__initContainers ?? [];
const pvc = env.__workspacePvc;
assert.ok(
volumeMounts.some((entry) => entry.name === workspace.volumeName && entry.mountPath === workspace.mountPath && entry.readOnly !== true),
"cloud-api workload Code Agent workspace writable mount"
);
assert.ok(
volumes.some((entry) => entry.name === workspace.volumeName && entry.persistentVolumeClaim?.claimName === workspace.claimName),
"cloud-api workload Code Agent workspace PVC volume"
);
assert.equal(pvc?.kind, "PersistentVolumeClaim", "Code Agent workspace PVC resource");
assert.equal(pvc?.metadata?.name, workspace.claimName, "Code Agent workspace PVC name");
assert.ok((pvc?.spec?.accessModes ?? []).includes(workspace.accessMode), "Code Agent workspace PVC uses ReadWriteOnce");
assert.equal(pvc?.spec?.resources?.requests?.storage, workspace.storage, "Code Agent workspace PVC requested storage");
const initContainer = initContainers.find((entry) => entry.name === workspace.initContainerName);
assert.ok(initContainer, "cloud-api workload initializes persistent Code Agent workspace");
assert.ok(
(initContainer?.volumeMounts ?? []).some((entry) => entry.name === workspace.volumeName && entry.mountPath === workspace.mountPath),
"Code Agent workspace init container mounts the persistent workspace"
);
const initCommand = [...(initContainer?.command ?? []), ...(initContainer?.args ?? [])].join("\n");
assert.ok(
initCommand.includes(workspace.sourcePath) && initCommand.includes(workspace.path) && initCommand.includes(workspace.initMarker),
"Code Agent workspace init container uses copy-once /app bootstrap"
);
assert.equal(env.__deploymentStrategy?.type, workspace.rolloutStrategy, "cloud-api uses Recreate rollout for single-writer workspace PVC");
}
function assertM3PatchPanelDeployContract(manifestService, workloadEnv) {
const route = {
fromResourceId: "res_boxsimu_1",