From e7b5b1ffb2534c59abb9e65f7f9e797f2754a0b2 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 10 Jun 2026 10:30:30 -0400 Subject: [PATCH 01/15] test(e2e): mark model router migration started --- .../migration/legacy-inventory.json | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/test/e2e-scenario/migration/legacy-inventory.json b/test/e2e-scenario/migration/legacy-inventory.json index e66981d726..ce380ec798 100644 --- a/test/e2e-scenario/migration/legacy-inventory.json +++ b/test/e2e-scenario/migration/legacy-inventory.json @@ -1,7 +1,12 @@ { "$comment": "SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.\nSPDX-License-Identifier: Apache-2.0", "version": 1, - "statusValues": ["not-migrated", "bridge-probe", "covered", "retired"], + "statusValues": [ + "not-migrated", + "bridge-probe", + "covered", + "retired" + ], "deletionReadiness": { "rule": "A legacy E2E entrypoint is deletion-ready only after equivalent Vitest scenario coverage exists, or after #4357 records an explicit retirement decision.", "requires": [ @@ -468,7 +473,7 @@ "legacyScript": "test/e2e/test-model-router-provider-routed-inference.sh", "domain": "inference", "ownerIssue": "#4349", - "status": "not-migrated", + "status": "bridge-probe", "targetVitestScenarios": [], "bridgeProbes": [], "retiredReason": "", @@ -601,11 +606,13 @@ "domain": "sandbox-lifecycle", "ownerIssue": "#4355", "status": "covered", - "targetVitestScenarios": ["test/e2e-scenario/live/openshell-version-pin.test.ts"], + "targetVitestScenarios": [ + "test/e2e-scenario/live/openshell-version-pin.test.ts" + ], "bridgeProbes": [], "retiredReason": "", "deletionReady": false, - "notes": "Covered by free-standing live test (PR #5107). Hermetic installer-script behavioral test — stubs PATH binaries, runs scripts/install-openshell.sh, asserts the four [PASS] outcomes from the legacy bash guard. Not registry-driven; dispatched as a discrete openshell-version-pin-vitest job in e2e-vitest-scenarios.yaml. Bash guard retained in regression-e2e.yaml; deletion is a follow-up PR with #4357 approval.", + "notes": "Covered by free-standing live test (PR #5107). Hermetic installer-script behavioral test \u2014 stubs PATH binaries, runs scripts/install-openshell.sh, asserts the four [PASS] outcomes from the legacy bash guard. Not registry-driven; dispatched as a discrete openshell-version-pin-vitest job in e2e-vitest-scenarios.yaml. Bash guard retained in regression-e2e.yaml; deletion is a follow-up PR with #4357 approval.", "frozenAtSha": "3d9c01931f598cf2450bd88028cb3b44bcf367b0", "convergenceEvidence": { "redRunUrl": null, @@ -816,7 +823,9 @@ "ownerIssue": "#4354", "status": "bridge-probe", "targetVitestScenarios": [], - "bridgeProbes": ["test/e2e/brev-e2e.test.ts"], + "bridgeProbes": [ + "test/e2e/brev-e2e.test.ts" + ], "retiredReason": "", "deletionReady": false, "notes": "Already uses Vitest, but still dispatches legacy remote shell suites; keep as a bridge until remote execution uses shared fixtures." @@ -825,7 +834,9 @@ "internalSurfaces": [ { "id": "typed-shell-orchestrators", - "paths": ["test/e2e-scenario/scenarios/orchestrators"], + "paths": [ + "test/e2e-scenario/scenarios/orchestrators" + ], "domain": "scenario-runner", "ownerIssue": "#4357", "status": "retired", @@ -839,7 +850,9 @@ }, { "id": "legacy-bash-scenario-workers", - "paths": ["test/e2e-scenario/nemoclaw_scenarios"], + "paths": [ + "test/e2e-scenario/nemoclaw_scenarios" + ], "domain": "scenario-runner", "ownerIssue": "#4357", "status": "retired", @@ -853,7 +866,9 @@ }, { "id": "legacy-onboarding-assertion-workers", - "paths": ["test/e2e-scenario/onboarding_assertions"], + "paths": [ + "test/e2e-scenario/onboarding_assertions" + ], "domain": "smoke-onboarding", "ownerIssue": "#4348", "status": "retired", @@ -867,7 +882,9 @@ }, { "id": "legacy-validation-suites", - "paths": ["test/e2e-scenario/validation_suites"], + "paths": [ + "test/e2e-scenario/validation_suites" + ], "domain": "runtime-suites", "ownerIssue": "#4357", "status": "retired", From 359ba8aa667a7c46f6950dd6be220c468c282853 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 10 Jun 2026 10:34:21 -0400 Subject: [PATCH 02/15] test(e2e): add model router routed completion helper --- .../framework-tests/e2e-phase-runtime.test.ts | 67 +++++++++++++++++++ test/e2e-scenario/framework/phases/runtime.ts | 58 ++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts b/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts index 926c54b5fe..6b16ee4dc1 100644 --- a/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts +++ b/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts @@ -192,6 +192,73 @@ describe("runtime phase fixture", () => { expect(call?.options?.artifactName).toBe("custom-chat"); }); + it("checks provider-routed Model Router completion semantics", async () => { + const runner = new FakeRunner(); + runner.enqueue( + shellResult( + 0, + JSON.stringify({ + model: "nvidia-routed", + choices: [{ message: { content: "PONG" } }], + }), + ), + ); + + await fixture(runner).expectModelRouterProviderRoutedCompletion(instance()); + + const call = runner.calls[0]; + expect(call?.command).toBe("openshell"); + expect(call?.args).toEqual([ + "sandbox", + "exec", + "e2e-ubuntu-repo-cloud-openclaw", + "--", + "curl", + "-fsS", + "--max-time", + "90", + "-H", + "Content-Type: application/json", + "--data-raw", + expect.any(String), + "https://inference.local/v1/chat/completions", + ]); + expect(JSON.parse(call?.args[11] ?? "{}")).toEqual({ + model: "nvidia-routed", + messages: [{ role: "user", content: "Reply with exactly one word: PONG" }], + max_tokens: 50, + }); + expect(call?.options?.artifactName).toBe("runtime-model-router-provider-routed-completion"); + }); + + it("rejects provider-routed Model Router completions with the wrong model", async () => { + const runner = new FakeRunner(); + runner.enqueue( + shellResult( + 0, + JSON.stringify({ choices: [{ message: { content: "PONG" } }], model: "default" }), + ), + ); + + await expect(fixture(runner).expectModelRouterProviderRoutedCompletion(instance())).rejects.toThrow( + "model-router provider-routed completion response model was not provider-routed", + ); + }); + + it("rejects provider-routed Model Router completions without PONG content", async () => { + const runner = new FakeRunner(); + runner.enqueue( + shellResult( + 0, + JSON.stringify({ choices: [{ message: { content: "hello" } }], model: "nvidia-routed" }), + ), + ); + + await expect(fixture(runner).expectModelRouterProviderRoutedCompletion(instance())).rejects.toThrow( + "model-router provider-routed completion response missing PONG content", + ); + }); + it("accepts configured status codes for auth-proxy and route-health checks", async () => { const runner = new FakeRunner(); runner.enqueue(shellResult(0, "403")); diff --git a/test/e2e-scenario/framework/phases/runtime.ts b/test/e2e-scenario/framework/phases/runtime.ts index e417d30c1e..2de7e54bdd 100644 --- a/test/e2e-scenario/framework/phases/runtime.ts +++ b/test/e2e-scenario/framework/phases/runtime.ts @@ -193,6 +193,30 @@ function assertChatCompletionShape(json: unknown, label: string): void { } } +function choiceContent(choice: unknown): string { + if (!choice || typeof choice !== "object") return ""; + const message = (choice as { message?: unknown }).message; + if (message && typeof message === "object") { + const content = (message as { content?: unknown }).content; + if (typeof content === "string") return content; + } + const text = (choice as { text?: unknown }).text; + return typeof text === "string" ? text : ""; +} + +function assertModelRouterPongCompletion(json: unknown, label: string): void { + assertChatCompletionShape(json, label); + const body = json as { model?: unknown; choices?: unknown }; + const model = typeof body.model === "string" ? body.model : ""; + if (model !== "nvidia-routed" && !model.startsWith("nvidia-routed")) { + throw new Error(`${label} response model was not provider-routed`); + } + const choices = Array.isArray(body.choices) ? body.choices : []; + if (!choices.some((choice) => /\bPONG\b/i.test(choiceContent(choice)))) { + throw new Error(`${label} response missing PONG content`); + } +} + function hasModelIdentifier(entry: unknown): boolean { if (typeof entry === "string") return entry.trim().length > 0; if (!entry || typeof entry !== "object") return false; @@ -301,6 +325,40 @@ export class RuntimePhaseFixture { return { endpoint, result }; } + async expectModelRouterProviderRoutedCompletion( + instance: NemoClawInstance, + options: InferenceRuntimeChatOptions & { readonly route?: InferenceRoute } = {}, + ): Promise { + const endpoint = inferenceRouteUrl(options.route, CHAT_COMPLETIONS_PATH); + const result = await this.sandbox.exec( + instance.sandboxName, + [ + "curl", + "-fsS", + "--max-time", + curlMaxTime({ ...options, curlMaxTimeSeconds: options.curlMaxTimeSeconds ?? 90 }), + "-H", + "Content-Type: application/json", + ...headerArgs(options.headers), + "--data-raw", + openAiChatPayload({ + ...options, + maxTokens: options.maxTokens ?? 50, + model: options.model ?? "nvidia-routed", + prompt: options.prompt ?? "Reply with exactly one word: PONG", + }), + endpoint, + ], + shellOptions(options, "runtime-model-router-provider-routed-completion"), + ); + assertExitZero(result, "model-router provider-routed completion probe"); + assertModelRouterPongCompletion( + parseJsonBody(result.stdout, "model-router provider-routed completion"), + "model-router provider-routed completion", + ); + return { endpoint, result }; + } + async expectInferenceLocalStatus( instance: NemoClawInstance, options: InferenceRuntimeStatusOptions = {}, From 71f4c0acd551a3252f807c1b015450cdf83c908f Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 10 Jun 2026 10:34:38 -0400 Subject: [PATCH 03/15] test(e2e): add provider routed manifest --- .../openclaw-nvidia-provider-routed.yaml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 test/e2e-scenario/manifests/openclaw-nvidia-provider-routed.yaml diff --git a/test/e2e-scenario/manifests/openclaw-nvidia-provider-routed.yaml b/test/e2e-scenario/manifests/openclaw-nvidia-provider-routed.yaml new file mode 100644 index 0000000000..335d0308ea --- /dev/null +++ b/test/e2e-scenario/manifests/openclaw-nvidia-provider-routed.yaml @@ -0,0 +1,24 @@ +apiVersion: nemoclaw.io/v1 +kind: NemoClawInstance +metadata: + name: openclaw-nvidia-provider-routed +spec: + setup: + install: + source: repo-current + runtime: + containerEngine: docker + containerDaemon: running + platform: + os: ubuntu + executionTarget: local + onboarding: + agent: openclaw + provider: routed + modelRoute: inference-local + policyTier: open + messaging: [] + state: + workspaceRef: default + credentialRefs: + - NVIDIA_API_KEY From a9feb1e8eacfadfafeac90302d99026b0b7c8276 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 10 Jun 2026 10:35:11 -0400 Subject: [PATCH 04/15] test(e2e): support provider routed onboarding fixture --- .../e2e-phase-onboarding.test.ts | 57 +++++++++++++++++++ .../framework/phases/onboarding.ts | 48 ++++++++++++++-- 2 files changed, 101 insertions(+), 4 deletions(-) diff --git a/test/e2e-scenario/framework-tests/e2e-phase-onboarding.test.ts b/test/e2e-scenario/framework-tests/e2e-phase-onboarding.test.ts index 7add981d6c..0d8c186c20 100644 --- a/test/e2e-scenario/framework-tests/e2e-phase-onboarding.test.ts +++ b/test/e2e-scenario/framework-tests/e2e-phase-onboarding.test.ts @@ -205,6 +205,63 @@ describe("onboarding phase fixture", () => { expect(runner.calls).toEqual([]); }); + it("runs provider-routed cloud OpenClaw onboarding with Model Router inputs", async () => { + const runner = new FakeRunner(); + runner.enqueue(shellResult(0, "onboarded\n")); + const cleanup = new FakeCleanup(); + const secrets = new FakeSecrets({ NVIDIA_API_KEY: "secret-token" }); + const onboard = new OnboardingPhaseFixture(new HostCliClient(runner), secrets, cleanup); + + const instance = await onboard.from( + ready({ onboarding: "cloud-openclaw-provider-routed" }), + { sandboxName: "e2e-provider-routed" }, + ); + + expect(instance).toMatchObject({ + onboarding: "cloud-openclaw-provider-routed", + sandboxName: "e2e-provider-routed", + agent: "openclaw", + provider: "nvidia", + providerEnv: "cloud", + }); + expect(secrets.requiredCalls).toEqual(["NVIDIA_API_KEY"]); + expect(cleanup.calls[0]?.name).toBe("destroy NemoClaw sandbox e2e-provider-routed"); + expect(runner.calls).toEqual([ + { + command: "nemoclaw", + args: ["onboard", "--non-interactive", "--yes", "--yes-i-accept-third-party-software"], + options: { + artifactName: "onboard-cloud-openclaw-provider-routed", + env: expect.objectContaining({ + NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1", + NEMOCLAW_AGENT: "openclaw", + NEMOCLAW_POLICY_TIER: "open", + NEMOCLAW_PROVIDER: "routed", + NEMOCLAW_PROVIDER_KEY: "secret-token", + NEMOCLAW_SANDBOX_NAME: "e2e-provider-routed", + NVIDIA_API_KEY: "secret-token", + PATH: expect.any(String), + }), + redactionValues: ["secret-token"], + timeoutMs: 900_000, + }, + }, + ]); + }); + + it("requires Docker for provider-routed cloud OpenClaw onboarding", async () => { + const onboard = new OnboardingPhaseFixture(new HostCliClient(new FakeRunner()), new FakeSecrets({ NVIDIA_API_KEY: "secret" })); + + await expect( + onboard.from( + ready({ + onboarding: "cloud-openclaw-provider-routed", + docker: { id: "docker-running", expectation: "required", available: false }, + }), + ), + ).rejects.toThrow(/cloud-openclaw-provider-routed onboarding requires an available Docker runtime/); + }); + it("requires Docker for cloud OpenClaw onboarding", async () => { const onboard = new OnboardingPhaseFixture( new HostCliClient(new FakeRunner()), diff --git a/test/e2e-scenario/framework/phases/onboarding.ts b/test/e2e-scenario/framework/phases/onboarding.ts index 817ff311f2..b2ca845beb 100644 --- a/test/e2e-scenario/framework/phases/onboarding.ts +++ b/test/e2e-scenario/framework/phases/onboarding.ts @@ -83,12 +83,16 @@ function sandboxNameFromOptions(onboarding: string, options: OnboardingOptions): return sandboxName; } -function commandEnv(sandboxName: string, extra: NodeJS.ProcessEnv = {}): NodeJS.ProcessEnv { +function commandEnv( + sandboxName: string, + provider: "cloud" | "routed", + extra: NodeJS.ProcessEnv = {}, +): NodeJS.ProcessEnv { return { ...buildAvailabilityProbeEnv(), ...extra, NEMOCLAW_AGENT: "openclaw", - NEMOCLAW_PROVIDER: "cloud", + NEMOCLAW_PROVIDER: provider, NEMOCLAW_SANDBOX_NAME: sandboxName, }; } @@ -153,6 +157,9 @@ export class OnboardingPhaseFixture { case "cloud-openclaw": result = await this.cloudOpenClaw(environment, options); break; + case "cloud-openclaw-provider-routed": + result = await this.cloudOpenClawProviderRouted(environment, options); + break; case "cloud-openclaw-no-docker": result = await this.cloudOpenClawNoDocker(environment, options); break; @@ -179,7 +186,7 @@ export class OnboardingPhaseFixture { this.registerSandboxCleanup(sandboxName); const result = await this.host.nemoclaw(ONBOARD_ARGS, { artifactName: "onboard-cloud-openclaw", - env: commandEnv(sandboxName, { NVIDIA_API_KEY: apiKey }), + env: commandEnv(sandboxName, "cloud", { NVIDIA_API_KEY: apiKey }), redactionValues: [apiKey], timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS, }); @@ -195,6 +202,39 @@ export class OnboardingPhaseFixture { }; } + async cloudOpenClawProviderRouted( + environment: EnvironmentReady, + options: OnboardingOptions = {}, + ): Promise { + if (!environment.docker.available) { + throw new Error("cloud-openclaw-provider-routed onboarding requires an available Docker runtime."); + } + const sandboxName = sandboxNameFromOptions(environment.onboarding, options); + const apiKey = this.secrets.required("NVIDIA_API_KEY"); + this.registerSandboxCleanup(sandboxName); + const result = await this.host.nemoclaw(ONBOARD_ARGS, { + artifactName: "onboard-cloud-openclaw-provider-routed", + env: commandEnv(sandboxName, "routed", { + NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1", + NEMOCLAW_POLICY_TIER: "open", + NEMOCLAW_PROVIDER_KEY: apiKey, + NVIDIA_API_KEY: apiKey, + }), + redactionValues: [apiKey], + timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS, + }); + assertExitZero(result, "cloud-openclaw-provider-routed onboarding"); + return { + onboarding: environment.onboarding, + sandboxName, + agent: "openclaw", + provider: "nvidia", + providerEnv: "cloud", + gatewayUrl: OPENCLAW_GATEWAY_URL, + result, + }; + } + async cloudOpenClawNoDocker( environment: EnvironmentReady, options: OnboardingOptions = {}, @@ -212,7 +252,7 @@ export class OnboardingPhaseFixture { try { await writeFile(shimPath, noDockerShim(), "utf8"); await chmod(shimPath, 0o700); - const env = commandEnv(sandboxName, { NVIDIA_API_KEY: apiKey }); + const env = commandEnv(sandboxName, "cloud", { NVIDIA_API_KEY: apiKey }); env.PATH = prependPath(shimDir, env.PATH); const result = await this.host.nemoclaw(ONBOARD_ARGS, { artifactName: "onboard-cloud-openclaw-no-docker", From d21819c0529c3727b7dac8a78e9a4cfefe297ad9 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 10 Jun 2026 10:35:17 -0400 Subject: [PATCH 05/15] test(e2e): allow provider routed live scenario --- test/e2e-scenario/scenarios/runtime-support.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e-scenario/scenarios/runtime-support.ts b/test/e2e-scenario/scenarios/runtime-support.ts index 6a9f874264..139ad9d38a 100644 --- a/test/e2e-scenario/scenarios/runtime-support.ts +++ b/test/e2e-scenario/scenarios/runtime-support.ts @@ -6,7 +6,7 @@ import type { ScenarioDefinition } from "./types.ts"; const SUPPORTED_PLATFORMS = new Set(["ubuntu-local"]); const SUPPORTED_INSTALLS = new Set(["repo-current"]); const SUPPORTED_RUNTIMES = new Set(["docker-running"]); -const SUPPORTED_ONBOARDING = new Set(["cloud-openclaw"]); +const SUPPORTED_ONBOARDING = new Set(["cloud-openclaw", "cloud-openclaw-provider-routed"]); // Lifecycle profiles wired into the live Vitest driver. A profile is // supported only after both (a) `LifecyclePhaseFixture.simulate(profile)` // dispatches it, and (b) at least one expected-state declares the post- From 30c888c40b8d440aa124284d15a19d094c654071 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 10 Jun 2026 10:36:26 -0400 Subject: [PATCH 06/15] test(e2e): register provider routed scenario --- .../e2e-live-registry-discovery.test.ts | 21 +++++++++++++++++++ .../e2e-scenario-matrix.test.ts | 17 ++++++++++++++- .../scenarios/scenarios/baseline.ts | 11 ++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/test/e2e-scenario/framework-tests/e2e-live-registry-discovery.test.ts b/test/e2e-scenario/framework-tests/e2e-live-registry-discovery.test.ts index 5f8968e33d..0cd1197ee6 100644 --- a/test/e2e-scenario/framework-tests/e2e-live-registry-discovery.test.ts +++ b/test/e2e-scenario/framework-tests/e2e-live-registry-discovery.test.ts @@ -57,6 +57,27 @@ describe("live Vitest registry discovery support", () => { ]); }); + it("wires the provider-routed Model Router guard through live fixtures", () => { + const scenario = listScenarios().find( + (entry) => entry.id === "ubuntu-repo-cloud-openclaw-provider-routed", + ); + + expect(scenario).toBeTruthy(); + expect(scenario!.environment?.onboarding).toBe("cloud-openclaw-provider-routed"); + expect(scenario!.suiteIds).toEqual(["smoke", "model-router"]); + expect(liveScenarioSupport(scenario!)).toMatchObject({ + supported: true, + reasons: [], + }); + expect(buildLiveScenarioRunPlan(scenario!)).toEqual({ + scenarioId: "ubuntu-repo-cloud-openclaw-provider-routed", + manifestPath: "test/e2e-scenario/manifests/openclaw-nvidia-provider-routed.yaml", + expectedStateId: "cloud-openclaw-ready", + suiteIds: ["smoke", "model-router"], + phases: ["environment", "onboarding", "state-validation"], + }); + }); + it("keeps unsupported onboarding profiles skipped with a concrete reason", () => { const scenario = listScenarios().find((entry) => entry.id === "ubuntu-repo-cloud-hermes"); diff --git a/test/e2e-scenario/framework-tests/e2e-scenario-matrix.test.ts b/test/e2e-scenario/framework-tests/e2e-scenario-matrix.test.ts index b46c1e88d3..560f7ddd3b 100644 --- a/test/e2e-scenario/framework-tests/e2e-scenario-matrix.test.ts +++ b/test/e2e-scenario/framework-tests/e2e-scenario-matrix.test.ts @@ -78,6 +78,7 @@ describe("live Vitest scenario matrix", () => { it("builds the default live Vitest matrix from fixture-supported scenarios only", () => { expect(buildLiveScenarioMatrix().map((entry) => entry.id)).toEqual([ "ubuntu-repo-cloud-openclaw", + "ubuntu-repo-cloud-openclaw-provider-routed", "ubuntu-repo-docker-post-reboot-recovery", ]); expect(buildLiveScenarioMatrix()[0]).toMatchObject({ @@ -93,11 +94,24 @@ describe("live Vitest scenario matrix", () => { supportReasons: [], pendingRuntimeSuites: ["smoke", "inference", "credentials"], }); + expect(buildLiveScenarioMatrix()[1]).toMatchObject({ + id: "ubuntu-repo-cloud-openclaw-provider-routed", + runner: "ubuntu-latest", + platform: "ubuntu-local", + install: "repo-current", + runtime: "docker-running", + onboarding: "cloud-openclaw-provider-routed", + expectedStateId: "cloud-openclaw-ready", + suites: ["smoke", "model-router"], + requiredSecrets: ["NVIDIA_API_KEY"], + supported: true, + supportReasons: [], + }); // Failing-test-first guard for #4423. Pinned in the matrix to // confirm the lifecycle whitelist + post-reboot-recovery scenario // are wired together; the actual RED/GREEN behavior is exercised // by the live runner (gates on the fix landing in src/lib/). - expect(buildLiveScenarioMatrix()[1]).toMatchObject({ + expect(buildLiveScenarioMatrix()[2]).toMatchObject({ id: "ubuntu-repo-docker-post-reboot-recovery", runner: "ubuntu-latest", platform: "ubuntu-local", @@ -129,6 +143,7 @@ describe("live Vitest scenario matrix", () => { const parsed = JSON.parse(lines[0]); expect(parsed.map((entry: { id: string }) => entry.id)).toEqual([ "ubuntu-repo-cloud-openclaw", + "ubuntu-repo-cloud-openclaw-provider-routed", "ubuntu-repo-docker-post-reboot-recovery", ]); }); diff --git a/test/e2e-scenario/scenarios/scenarios/baseline.ts b/test/e2e-scenario/scenarios/scenarios/baseline.ts index f4b3c4209c..df146dc106 100644 --- a/test/e2e-scenario/scenarios/scenarios/baseline.ts +++ b/test/e2e-scenario/scenarios/scenarios/baseline.ts @@ -183,6 +183,17 @@ const canonicalScenarioInputs: CanonicalScenarioInput[] = [ "Failing-test-first guard for #4423: post-reboot recovery must preserve " + "the local registry entry and restart the labeled Docker container.", }, + { + id: "ubuntu-repo-cloud-openclaw-provider-routed", + manifestName: "openclaw-nvidia-provider-routed", + environment: ubuntuRepoDocker("cloud-openclaw-provider-routed"), + expectedStateId: "cloud-openclaw-ready", + suiteIds: ["smoke", "model-router"], + requiredSecrets: ["NVIDIA_API_KEY"], + description: + "Coverage guard for #3255: Model Router provider-routed onboarding must " + + "produce a working inference.local routed completion.", + }, { id: "ubuntu-repo-openai-compatible-openclaw", manifestName: "openclaw-openai-compatible", From d631c1849f9b25b9c07717af54553495cfa1fca0 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 10 Jun 2026 10:37:37 -0400 Subject: [PATCH 07/15] test(e2e): add model router health helper --- .../framework-tests/e2e-phase-runtime.test.ts | 26 ++++++++++++++ test/e2e-scenario/framework/phases/runtime.ts | 34 ++++++++++++++++--- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts b/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts index 6b16ee4dc1..cf3a6f1eb6 100644 --- a/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts +++ b/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts @@ -192,6 +192,32 @@ describe("runtime phase fixture", () => { expect(call?.options?.artifactName).toBe("custom-chat"); }); + it("checks host model-router health has a healthy endpoint", async () => { + const runner = new FakeRunner(); + runner.enqueue(shellResult(0, JSON.stringify({ healthy_count: 1 }))); + + const result = await fixture(runner).expectModelRouterHealthyEndpoint(); + + expect(result.endpoint).toBe("http://127.0.0.1:4000/health"); + expect(runner.calls[0]).toEqual({ + command: "curl", + args: ["-fsS", "--max-time", "10", "http://127.0.0.1:4000/health"], + options: { + artifactName: "runtime-model-router-health", + redactionValues: [], + }, + }); + }); + + it("rejects host model-router health without a healthy endpoint", async () => { + const runner = new FakeRunner(); + runner.enqueue(shellResult(0, JSON.stringify({ healthy_count: 0 }))); + + await expect(fixture(runner).expectModelRouterHealthyEndpoint()).rejects.toThrow( + "model-router health response reported no healthy endpoints", + ); + }); + it("checks provider-routed Model Router completion semantics", async () => { const runner = new FakeRunner(); runner.enqueue( diff --git a/test/e2e-scenario/framework/phases/runtime.ts b/test/e2e-scenario/framework/phases/runtime.ts index 2de7e54bdd..11aa6d302c 100644 --- a/test/e2e-scenario/framework/phases/runtime.ts +++ b/test/e2e-scenario/framework/phases/runtime.ts @@ -3,11 +3,12 @@ import { buildAvailabilityProbeEnv } from "../availability-env.ts"; import { assertExitZero } from "../clients/command.ts"; -import type { - ProviderClient, - ProviderJsonRequestOptions, - SandboxClient, - TrustedProviderEndpoint, +import { + trustedProviderEndpoint, + type ProviderClient, + type ProviderJsonRequestOptions, + type SandboxClient, + type TrustedProviderEndpoint, } from "../clients/index.ts"; import type { ShellProbeResult, ShellProbeRunOptions } from "../shell-probe.ts"; import type { NemoClawInstance } from "./onboarding.ts"; @@ -55,6 +56,7 @@ const DEFAULT_CHAT_PROMPT = "Say ok"; const DEFAULT_CHAT_MAX_TOKENS = 8; const MODELS_PATH = "/v1/models"; const CHAT_COMPLETIONS_PATH = "/v1/chat/completions"; +const MODEL_ROUTER_HEALTH_ENDPOINT = trustedProviderEndpoint("http://127.0.0.1:4000/health"); const SENSITIVE_HEADER_NAME = /(authorization|api[-_]?key|token|secret|credential|password)/i; function inferenceHost(route: InferenceRoute = "inference-local"): string { @@ -238,6 +240,16 @@ function assertModelListShape(json: unknown, label: string): void { } } +function assertModelRouterHealthyEndpoint(json: unknown, label: string): void { + if (!json || typeof json !== "object") { + throw new Error(`${label} response was not an object`); + } + const healthyCount = (json as { healthy_count?: unknown }).healthy_count; + if (typeof healthyCount !== "number" || healthyCount < 1) { + throw new Error(`${label} response reported no healthy endpoints`); + } +} + function providerRequestOptions( options: ProviderRuntimeRequestOptions, body?: string, @@ -359,6 +371,18 @@ export class RuntimePhaseFixture { return { endpoint, result }; } + async expectModelRouterHealthyEndpoint( + options: ProviderRuntimeRequestOptions = {}, + ): Promise { + const response = await this.provider.requestJson(MODEL_ROUTER_HEALTH_ENDPOINT, { + artifactName: "runtime-model-router-health", + curlMaxTimeSeconds: 10, + ...options, + }); + assertModelRouterHealthyEndpoint(response.json, "model-router health"); + return { endpoint: MODEL_ROUTER_HEALTH_ENDPOINT.logLabel, result: response.result }; + } + async expectInferenceLocalStatus( instance: NemoClawInstance, options: InferenceRuntimeStatusOptions = {}, From 725e049d120a9fea53a7574b5f9fb446869b09f9 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 10 Jun 2026 10:38:47 -0400 Subject: [PATCH 08/15] test(e2e): run model router assertions live --- .../framework-tests/e2e-live-registry-discovery.test.ts | 2 +- test/e2e-scenario/live/registry-scenarios.test.ts | 9 +++++++++ test/e2e-scenario/live/run-plan.ts | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/test/e2e-scenario/framework-tests/e2e-live-registry-discovery.test.ts b/test/e2e-scenario/framework-tests/e2e-live-registry-discovery.test.ts index 0cd1197ee6..cbe40fede0 100644 --- a/test/e2e-scenario/framework-tests/e2e-live-registry-discovery.test.ts +++ b/test/e2e-scenario/framework-tests/e2e-live-registry-discovery.test.ts @@ -74,7 +74,7 @@ describe("live Vitest registry discovery support", () => { manifestPath: "test/e2e-scenario/manifests/openclaw-nvidia-provider-routed.yaml", expectedStateId: "cloud-openclaw-ready", suiteIds: ["smoke", "model-router"], - phases: ["environment", "onboarding", "state-validation"], + phases: ["environment", "onboarding", "state-validation", "runtime"], }); }); diff --git a/test/e2e-scenario/live/registry-scenarios.test.ts b/test/e2e-scenario/live/registry-scenarios.test.ts index 7391f20dcd..bed86c5cb8 100644 --- a/test/e2e-scenario/live/registry-scenarios.test.ts +++ b/test/e2e-scenario/live/registry-scenarios.test.ts @@ -92,11 +92,20 @@ for (const scenario of listScenarios()) { const validation = await stateValidation.from(scenario.expectedStateId, instance); + const runtimeSteps: string[] = []; + if (scenario.suiteIds?.includes("model-router")) { + await runtime.expectModelRouterHealthyEndpoint(); + runtimeSteps.push("runtime.model-router.healthy-endpoint"); + await runtime.expectModelRouterProviderRoutedCompletion(instance); + runtimeSteps.push("runtime.model-router.provider-routed-completion"); + } + await artifacts.writeJson("scenario-result.json", { id: scenario.id, expectedStateId: validation.state.id, probes: validation.probes.map((probe) => probe.id), pendingRuntimeSuites: support.pendingRuntimeSuites, + runtimeSteps, lifecycle: lifecycleResult ? { profile: lifecycleResult.profile, steps: lifecycleResult.steps.map((s) => s.id) } : undefined, diff --git a/test/e2e-scenario/live/run-plan.ts b/test/e2e-scenario/live/run-plan.ts index 3e0c659ff3..f7743d07f3 100644 --- a/test/e2e-scenario/live/run-plan.ts +++ b/test/e2e-scenario/live/run-plan.ts @@ -22,6 +22,7 @@ export function buildLiveScenarioRunPlan(scenario: ScenarioDefinition): LiveScen "onboarding", ...(scenario.environment?.lifecycle ? ["lifecycle"] : []), "state-validation", + ...(scenario.suiteIds?.includes("model-router") ? ["runtime"] : []), ], }; } From d0c03ae607a0554093d77ae854c8d62fbfbd151f Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 10 Jun 2026 10:41:43 -0400 Subject: [PATCH 09/15] test(e2e): fix model router live runtime types --- test/e2e-scenario/framework/phases/runtime.ts | 5 +++-- test/e2e-scenario/live/registry-scenarios.test.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/e2e-scenario/framework/phases/runtime.ts b/test/e2e-scenario/framework/phases/runtime.ts index 11aa6d302c..e17c97e26c 100644 --- a/test/e2e-scenario/framework/phases/runtime.ts +++ b/test/e2e-scenario/framework/phases/runtime.ts @@ -375,9 +375,10 @@ export class RuntimePhaseFixture { options: ProviderRuntimeRequestOptions = {}, ): Promise { const response = await this.provider.requestJson(MODEL_ROUTER_HEALTH_ENDPOINT, { - artifactName: "runtime-model-router-health", - curlMaxTimeSeconds: 10, ...options, + artifactName: options.artifactName ?? "runtime-model-router-health", + curlMaxTimeSeconds: options.curlMaxTimeSeconds ?? 10, + redactionValues: options.redactionValues ? [...options.redactionValues] : undefined, }); assertModelRouterHealthyEndpoint(response.json, "model-router health"); return { endpoint: MODEL_ROUTER_HEALTH_ENDPOINT.logLabel, result: response.result }; diff --git a/test/e2e-scenario/live/registry-scenarios.test.ts b/test/e2e-scenario/live/registry-scenarios.test.ts index bed86c5cb8..7352eb520b 100644 --- a/test/e2e-scenario/live/registry-scenarios.test.ts +++ b/test/e2e-scenario/live/registry-scenarios.test.ts @@ -38,7 +38,7 @@ for (const scenario of listScenarios()) { test( liveScenarioTestName(scenario), - async ({ artifacts, environment, lifecycle, onboard, secrets, stateValidation }) => { + async ({ artifacts, environment, lifecycle, onboard, runtime, secrets, stateValidation }) => { for (const secret of scenario.requiredSecrets ?? []) { secrets.required(secret); } From 078116c162ffe366eced9737d56d93eb98052a6b Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 10 Jun 2026 10:41:58 -0400 Subject: [PATCH 10/15] style(e2e): format provider routed changes --- .../framework-tests/e2e-phase-onboarding.test.ts | 16 ++++++++++------ .../framework-tests/e2e-phase-runtime.test.ts | 10 ++++++---- test/e2e-scenario/framework/phases/onboarding.ts | 4 +++- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/test/e2e-scenario/framework-tests/e2e-phase-onboarding.test.ts b/test/e2e-scenario/framework-tests/e2e-phase-onboarding.test.ts index 0d8c186c20..8486b84806 100644 --- a/test/e2e-scenario/framework-tests/e2e-phase-onboarding.test.ts +++ b/test/e2e-scenario/framework-tests/e2e-phase-onboarding.test.ts @@ -212,10 +212,9 @@ describe("onboarding phase fixture", () => { const secrets = new FakeSecrets({ NVIDIA_API_KEY: "secret-token" }); const onboard = new OnboardingPhaseFixture(new HostCliClient(runner), secrets, cleanup); - const instance = await onboard.from( - ready({ onboarding: "cloud-openclaw-provider-routed" }), - { sandboxName: "e2e-provider-routed" }, - ); + const instance = await onboard.from(ready({ onboarding: "cloud-openclaw-provider-routed" }), { + sandboxName: "e2e-provider-routed", + }); expect(instance).toMatchObject({ onboarding: "cloud-openclaw-provider-routed", @@ -250,7 +249,10 @@ describe("onboarding phase fixture", () => { }); it("requires Docker for provider-routed cloud OpenClaw onboarding", async () => { - const onboard = new OnboardingPhaseFixture(new HostCliClient(new FakeRunner()), new FakeSecrets({ NVIDIA_API_KEY: "secret" })); + const onboard = new OnboardingPhaseFixture( + new HostCliClient(new FakeRunner()), + new FakeSecrets({ NVIDIA_API_KEY: "secret" }), + ); await expect( onboard.from( @@ -259,7 +261,9 @@ describe("onboarding phase fixture", () => { docker: { id: "docker-running", expectation: "required", available: false }, }), ), - ).rejects.toThrow(/cloud-openclaw-provider-routed onboarding requires an available Docker runtime/); + ).rejects.toThrow( + /cloud-openclaw-provider-routed onboarding requires an available Docker runtime/, + ); }); it("requires Docker for cloud OpenClaw onboarding", async () => { diff --git a/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts b/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts index cf3a6f1eb6..420b24e60f 100644 --- a/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts +++ b/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts @@ -266,7 +266,9 @@ describe("runtime phase fixture", () => { ), ); - await expect(fixture(runner).expectModelRouterProviderRoutedCompletion(instance())).rejects.toThrow( + await expect( + fixture(runner).expectModelRouterProviderRoutedCompletion(instance()), + ).rejects.toThrow( "model-router provider-routed completion response model was not provider-routed", ); }); @@ -280,9 +282,9 @@ describe("runtime phase fixture", () => { ), ); - await expect(fixture(runner).expectModelRouterProviderRoutedCompletion(instance())).rejects.toThrow( - "model-router provider-routed completion response missing PONG content", - ); + await expect( + fixture(runner).expectModelRouterProviderRoutedCompletion(instance()), + ).rejects.toThrow("model-router provider-routed completion response missing PONG content"); }); it("accepts configured status codes for auth-proxy and route-health checks", async () => { diff --git a/test/e2e-scenario/framework/phases/onboarding.ts b/test/e2e-scenario/framework/phases/onboarding.ts index b2ca845beb..a3a77ca713 100644 --- a/test/e2e-scenario/framework/phases/onboarding.ts +++ b/test/e2e-scenario/framework/phases/onboarding.ts @@ -207,7 +207,9 @@ export class OnboardingPhaseFixture { options: OnboardingOptions = {}, ): Promise { if (!environment.docker.available) { - throw new Error("cloud-openclaw-provider-routed onboarding requires an available Docker runtime."); + throw new Error( + "cloud-openclaw-provider-routed onboarding requires an available Docker runtime.", + ); } const sandboxName = sandboxNameFromOptions(environment.onboarding, options); const apiKey = this.secrets.required("NVIDIA_API_KEY"); From f3630302df8fff903b19b826c49ac5f251dd0100 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 10 Jun 2026 10:52:56 -0400 Subject: [PATCH 11/15] test(e2e): use named sandbox exec --- test/e2e-scenario/framework-tests/e2e-clients.test.ts | 3 ++- .../framework-tests/e2e-phase-runtime.test.ts | 8 ++++++-- test/e2e-scenario/framework/clients/sandbox.ts | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/test/e2e-scenario/framework-tests/e2e-clients.test.ts b/test/e2e-scenario/framework-tests/e2e-clients.test.ts index 77e8824fe2..33a50f6fb0 100644 --- a/test/e2e-scenario/framework-tests/e2e-clients.test.ts +++ b/test/e2e-scenario/framework-tests/e2e-clients.test.ts @@ -145,7 +145,7 @@ describe("E2E fixture clients", () => { expect(runner.calls[0]).toEqual({ command: "openshell", - args: ["sandbox", "exec", "assistant", "--", "echo", "ok"], + args: ["sandbox", "exec", "--name", "assistant", "--", "echo", "ok"], options: { artifactName: "sandbox-exec-assistant", }, @@ -192,6 +192,7 @@ describe("E2E fixture clients", () => { expect(runner.calls[0]?.args).toEqual([ "sandbox", "exec", + "--name", "assistant", "--", "sh", diff --git a/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts b/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts index 420b24e60f..319cf38588 100644 --- a/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts +++ b/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts @@ -111,6 +111,7 @@ describe("runtime phase fixture", () => { args: [ "sandbox", "exec", + "--name", "e2e-ubuntu-repo-cloud-openclaw", "--", "curl", @@ -170,6 +171,7 @@ describe("runtime phase fixture", () => { expect(call?.args).toEqual([ "sandbox", "exec", + "--name", "e2e-ubuntu-repo-cloud-openclaw", "--", "curl", @@ -183,7 +185,7 @@ describe("runtime phase fixture", () => { "https://inference.local/v1/chat/completions", ]); expect(call?.args).not.toContain("sh"); - const payload = JSON.parse(call?.args[11] ?? "{}"); + const payload = JSON.parse(call?.args[12] ?? "{}"); expect(payload).toEqual({ model: "default", messages: [{ role: "user", content: "Reply with ok" }], @@ -237,6 +239,7 @@ describe("runtime phase fixture", () => { expect(call?.args).toEqual([ "sandbox", "exec", + "--name", "e2e-ubuntu-repo-cloud-openclaw", "--", "curl", @@ -249,7 +252,7 @@ describe("runtime phase fixture", () => { expect.any(String), "https://inference.local/v1/chat/completions", ]); - expect(JSON.parse(call?.args[11] ?? "{}")).toEqual({ + expect(JSON.parse(call?.args[12] ?? "{}")).toEqual({ model: "nvidia-routed", messages: [{ role: "user", content: "Reply with exactly one word: PONG" }], max_tokens: 50, @@ -302,6 +305,7 @@ describe("runtime phase fixture", () => { args: [ "sandbox", "exec", + "--name", "e2e-ubuntu-repo-cloud-openclaw", "--", "curl", diff --git a/test/e2e-scenario/framework/clients/sandbox.ts b/test/e2e-scenario/framework/clients/sandbox.ts index 795060dad3..b80a44cbe5 100644 --- a/test/e2e-scenario/framework/clients/sandbox.ts +++ b/test/e2e-scenario/framework/clients/sandbox.ts @@ -50,7 +50,7 @@ export class SandboxClient { options: ShellProbeRunOptions = {}, ): Promise { validateSandboxName(name); - return this.openshell(["sandbox", "exec", name, "--", ...command], { + return this.openshell(["sandbox", "exec", "--name", name, "--", ...command], { artifactName: `sandbox-exec-${name}`, ...options, }); From cb9e9a2b4b2b6069d766f507ef8d26576ba05c59 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 10 Jun 2026 11:04:46 -0400 Subject: [PATCH 12/15] test(e2e): avoid truncating routed pong response --- .../framework-tests/e2e-phase-runtime.test.ts | 9 +++++++-- test/e2e-scenario/framework/phases/runtime.ts | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts b/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts index 319cf38588..972180828d 100644 --- a/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts +++ b/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts @@ -254,8 +254,13 @@ describe("runtime phase fixture", () => { ]); expect(JSON.parse(call?.args[12] ?? "{}")).toEqual({ model: "nvidia-routed", - messages: [{ role: "user", content: "Reply with exactly one word: PONG" }], - max_tokens: 50, + messages: [ + { + role: "user", + content: "Reply with exactly one word: PONG. Do not explain or include any other text.", + }, + ], + max_tokens: 256, }); expect(call?.options?.artifactName).toBe("runtime-model-router-provider-routed-completion"); }); diff --git a/test/e2e-scenario/framework/phases/runtime.ts b/test/e2e-scenario/framework/phases/runtime.ts index e17c97e26c..3b55d8e581 100644 --- a/test/e2e-scenario/framework/phases/runtime.ts +++ b/test/e2e-scenario/framework/phases/runtime.ts @@ -355,9 +355,9 @@ export class RuntimePhaseFixture { "--data-raw", openAiChatPayload({ ...options, - maxTokens: options.maxTokens ?? 50, + maxTokens: options.maxTokens ?? 256, model: options.model ?? "nvidia-routed", - prompt: options.prompt ?? "Reply with exactly one word: PONG", + prompt: options.prompt ?? "Reply with exactly one word: PONG. Do not explain or include any other text.", }), endpoint, ], From c7cf9700e2623c575a736eda5870c30f913086f2 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 10 Jun 2026 11:35:48 -0400 Subject: [PATCH 13/15] test(e2e): update sandbox exec expectation --- .../framework-tests/e2e-phase-state-validation.test.ts | 1 + test/e2e-scenario/framework/phases/runtime.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/test/e2e-scenario/framework-tests/e2e-phase-state-validation.test.ts b/test/e2e-scenario/framework-tests/e2e-phase-state-validation.test.ts index ca69fc221b..fdd194ace5 100644 --- a/test/e2e-scenario/framework-tests/e2e-phase-state-validation.test.ts +++ b/test/e2e-scenario/framework-tests/e2e-phase-state-validation.test.ts @@ -222,6 +222,7 @@ describe("state-validation phase fixture", () => { args: [ "sandbox", "exec", + "--name", "e2e-ubuntu-repo-cloud-openclaw", "--", "curl", diff --git a/test/e2e-scenario/framework/phases/runtime.ts b/test/e2e-scenario/framework/phases/runtime.ts index 3b55d8e581..2049e761a9 100644 --- a/test/e2e-scenario/framework/phases/runtime.ts +++ b/test/e2e-scenario/framework/phases/runtime.ts @@ -357,7 +357,9 @@ export class RuntimePhaseFixture { ...options, maxTokens: options.maxTokens ?? 256, model: options.model ?? "nvidia-routed", - prompt: options.prompt ?? "Reply with exactly one word: PONG. Do not explain or include any other text.", + prompt: + options.prompt ?? + "Reply with exactly one word: PONG. Do not explain or include any other text.", }), endpoint, ], From 3ae0a5c008b91462c905325362a2cbac8c42069c Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 10 Jun 2026 11:40:43 -0400 Subject: [PATCH 14/15] test(e2e): mark model router migration covered --- test/e2e-scenario/migration/legacy-inventory.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/e2e-scenario/migration/legacy-inventory.json b/test/e2e-scenario/migration/legacy-inventory.json index ce380ec798..a89378b09a 100644 --- a/test/e2e-scenario/migration/legacy-inventory.json +++ b/test/e2e-scenario/migration/legacy-inventory.json @@ -473,12 +473,14 @@ "legacyScript": "test/e2e/test-model-router-provider-routed-inference.sh", "domain": "inference", "ownerIssue": "#4349", - "status": "bridge-probe", - "targetVitestScenarios": [], + "status": "covered", + "targetVitestScenarios": [ + "test/e2e-scenario/live/registry-scenarios.test.ts" + ], "bridgeProbes": [], "retiredReason": "", "deletionReady": false, - "notes": "Initial completeness row; classify detailed coverage and deletion evidence in the owning migration issue before deleting." + "notes": "Covered by registry-driven live Vitest scenario ubuntu-repo-cloud-openclaw-provider-routed in PR #5151. Preserves ubuntu-latest runner, NVIDIA_API_KEY secret, provider-routed onboarding, host model-router health, and inference.local nvidia-routed PONG completion checks. Bash guard retained in regression-e2e.yaml; deletion remains a follow-up after #4357 approval." }, { "legacyScript": "test/e2e/test-ollama-auth-proxy-e2e.sh", From 8397f0b7a25bb9c0a574d50845c1a74a8f75db12 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Wed, 10 Jun 2026 12:05:14 -0400 Subject: [PATCH 15/15] test(e2e): redact router health credentials --- .../framework-tests/e2e-phase-runtime.test.ts | 23 ++++++++++++++++--- test/e2e-scenario/framework/phases/runtime.ts | 14 ++++++----- .../openclaw-nvidia-provider-routed.yaml | 3 +++ 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts b/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts index 972180828d..5a7404333a 100644 --- a/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts +++ b/test/e2e-scenario/framework-tests/e2e-phase-runtime.test.ts @@ -198,15 +198,32 @@ describe("runtime phase fixture", () => { const runner = new FakeRunner(); runner.enqueue(shellResult(0, JSON.stringify({ healthy_count: 1 }))); - const result = await fixture(runner).expectModelRouterHealthyEndpoint(); + const result = await fixture(runner).expectModelRouterHealthyEndpoint({ + apiKey: "provider-secret", + headers: ["X-Api-Key: header-secret"], + }); expect(result.endpoint).toBe("http://127.0.0.1:4000/health"); expect(runner.calls[0]).toEqual({ command: "curl", - args: ["-fsS", "--max-time", "10", "http://127.0.0.1:4000/health"], + args: [ + "-fsS", + "--max-time", + "10", + "-H", + "X-Api-Key: header-secret", + "-H", + "Authorization: Bearer provider-secret", + "http://127.0.0.1:4000/health", + ], options: { artifactName: "runtime-model-router-health", - redactionValues: [], + redactionValues: expect.arrayContaining([ + "X-Api-Key: header-secret", + "header-secret", + "provider-secret", + ]), + timeoutMs: 60_000, }, }); }); diff --git a/test/e2e-scenario/framework/phases/runtime.ts b/test/e2e-scenario/framework/phases/runtime.ts index 2049e761a9..87b8a945e9 100644 --- a/test/e2e-scenario/framework/phases/runtime.ts +++ b/test/e2e-scenario/framework/phases/runtime.ts @@ -376,12 +376,14 @@ export class RuntimePhaseFixture { async expectModelRouterHealthyEndpoint( options: ProviderRuntimeRequestOptions = {}, ): Promise { - const response = await this.provider.requestJson(MODEL_ROUTER_HEALTH_ENDPOINT, { - ...options, - artifactName: options.artifactName ?? "runtime-model-router-health", - curlMaxTimeSeconds: options.curlMaxTimeSeconds ?? 10, - redactionValues: options.redactionValues ? [...options.redactionValues] : undefined, - }); + const response = await this.provider.requestJson( + MODEL_ROUTER_HEALTH_ENDPOINT, + providerRequestOptions({ + ...options, + artifactName: options.artifactName ?? "runtime-model-router-health", + curlMaxTimeSeconds: options.curlMaxTimeSeconds ?? 10, + }), + ); assertModelRouterHealthyEndpoint(response.json, "model-router health"); return { endpoint: MODEL_ROUTER_HEALTH_ENDPOINT.logLabel, result: response.result }; } diff --git a/test/e2e-scenario/manifests/openclaw-nvidia-provider-routed.yaml b/test/e2e-scenario/manifests/openclaw-nvidia-provider-routed.yaml index 335d0308ea..d136f0ca24 100644 --- a/test/e2e-scenario/manifests/openclaw-nvidia-provider-routed.yaml +++ b/test/e2e-scenario/manifests/openclaw-nvidia-provider-routed.yaml @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + apiVersion: nemoclaw.io/v1 kind: NemoClawInstance metadata: