From b058f6effb9c4a3b7813e917eaf9bf609ba9e6cd Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 3 Aug 2026 01:32:15 -0700 Subject: [PATCH 1/3] test(e2e): require Hermes adapter lifecycle lanes Signed-off-by: Julie Yaunches Signed-off-by: Carlos Villela --- test/e2e/README.md | 10 ++- test/pr-risk-plan.test.ts | 100 +++++++++++++++++++++++++++--- tools/advisors/risk-plan.mts | 20 +++++- tools/pr-review-advisor/README.md | 13 +++- 4 files changed, 127 insertions(+), 16 deletions(-) diff --git a/test/e2e/README.md b/test/e2e/README.md index 815192f13c0..492619fa760 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -616,13 +616,17 @@ controller completes it as cancelled with `Superseded by PR update` or `PR closed — gate no longer applies` and identifies the obsolete head and base. The closed-PR outcome also applies when a fork repository was deleted and GitHub consequently returns no head-repository object. -Shared sandbox-boundary changes have a floor of `full-e2e`, `hermes-e2e`, and -`security-posture`. E2E control-plane changes select `cloud-onboard`, -`cloud-inference`, and `security-posture`. The `e2e-control-plane` +Shared sandbox-boundary changes have a floor of `full-e2e`, `hermes-e2e`, +`hermes-inference-switch`, and `security-posture`. E2E control-plane changes +select `cloud-onboard`, `cloud-inference`, and `security-posture`. The `e2e-control-plane` family remains the conservative boundary for shared E2E tools, workflow and security files, unknown live test paths, risk policy, dependency and test configuration, and preparation and upload actions. These cross-cutting changes keep the broad three-job floor. +Changes to the Hermes CLI wrapper, adapter manifest, or adapter validator also +select `channels-stop-start` and `mcp-bridge`. Both jobs include the Hermes +shard. The shards exercise the adapter during messaging-channel disable/re-enable +and managed MCP add/restart/remove operations. Repository-root `Dockerfile` changes additionally select `full-e2e` alongside the platform-install `cloud-onboard` floor so OpenClaw final-image changes run through cold onboarding and a real first turn. diff --git a/test/pr-risk-plan.test.ts b/test/pr-risk-plan.test.ts index df23eeb24ca..a7a136d5357 100644 --- a/test/pr-risk-plan.test.ts +++ b/test/pr-risk-plan.test.ts @@ -16,6 +16,13 @@ import { import { classifyTestDepth } from "../tools/pr-review-advisor/analyze.mts"; const HEAD_SHA = "a".repeat(40); +const HERMES_SANDBOX_BOUNDARY_JOBS = [ + "full-e2e", + "hermes-e2e", + "hermes-inference-switch", + "security-posture", +]; +const HERMES_CLI_ADAPTER_JOBS = ["channels-stop-start", "mcp-bridge"]; const HERMES_MANAGED_POLICY_JOBS = [ "bedrock-runtime-compatible-anthropic", "channels-stop-start", @@ -25,6 +32,28 @@ const HERMES_MANAGED_POLICY_JOBS = [ "hermes-shields-config", "security-posture", ]; +const HERMES_CLI_ADAPTER_REQUIRED_JOBS = [ + ...HERMES_SANDBOX_BOUNDARY_JOBS, + ...HERMES_CLI_ADAPTER_JOBS, +]; +const HERMES_MANAGED_POLICY_REQUIRED_JOBS = [ + ...HERMES_SANDBOX_BOUNDARY_JOBS, + "bedrock-runtime-compatible-anthropic", + "channels-stop-start", + "dashboard-remote-bind", + "hermes-shields-config", +]; +const HERMES_WRAPPER_FOCUSED_JOBS = [ + "bedrock-runtime-compatible-anthropic", + "channels-stop-start", + "dashboard-remote-bind", + "hermes-e2e", + "hermes-inference-switch", + "hermes-shields-config", + "mcp-bridge", + "security-posture", +]; +const HERMES_WRAPPER_REQUIRED_JOBS = [...HERMES_MANAGED_POLICY_REQUIRED_JOBS, "mcp-bridge"]; const HERMES_MANAGED_POLICY_FILES = [ "agents/hermes/config/managed-policy.ts", "agents/hermes/hermes-wrapper.py", @@ -46,7 +75,7 @@ describe("deterministic PR risk plan", () => { const second = plan("src/lib/onboard.ts", "src/lib/state/registry.ts"); expect(first).toEqual(second); - expect(first.version).toBe(11); + expect(first.version).toBe(12); expect(first.headSha).toBe(HEAD_SHA); expect(first.planHash).toMatch(/^[a-f0-9]{64}$/u); expect(first.changedFiles).toEqual(["src/lib/onboard.ts", "src/lib/state/registry.ts"]); @@ -136,30 +165,76 @@ describe("deterministic PR risk plan", () => { ]); }); + it.each([ + "agents/hermes/hermes-cli-adapter-v1.json", + "agents/hermes/hermes-wrapper.py", + "agents/hermes/validate-cli-adapter.py", + ])("selects Hermes MCP and channel lifecycle E2E for %s (#8011)", (changedFile) => { + const result = plan(changedFile); + const isWrapper = changedFile === "agents/hermes/hermes-wrapper.py"; + const expectedFocusedJobs = isWrapper ? HERMES_WRAPPER_FOCUSED_JOBS : HERMES_CLI_ADAPTER_JOBS; + const expectedRequiredJobs = isWrapper + ? HERMES_WRAPPER_REQUIRED_JOBS + : HERMES_CLI_ADAPTER_REQUIRED_JOBS; + + const focusedFamily = result.families.find((family) => family.id === "focused-e2e"); + expect(focusedFamily).toEqual( + expect.objectContaining({ + matchedFiles: [changedFile], + requiredJobs: expectedFocusedJobs, + }), + ); + expect(riskPlanRequiredJobIds(result)).toEqual(expectedRequiredJobs); + }); + it.each( HERMES_MANAGED_POLICY_FILES, )("selects every Hermes managed-policy live E2E job for %s (#8008)", (changedFile) => { const result = plan(changedFile); - - expect(result.families).toContainEqual( + const isWrapper = changedFile === "agents/hermes/hermes-wrapper.py"; + const expectedFocusedJobs = isWrapper + ? HERMES_WRAPPER_FOCUSED_JOBS + : HERMES_MANAGED_POLICY_JOBS; + const expectedRequiredJobs = isWrapper + ? HERMES_WRAPPER_REQUIRED_JOBS + : changedFile === "src/lib/hermes-managed-route.ts" + ? HERMES_MANAGED_POLICY_JOBS + : HERMES_MANAGED_POLICY_REQUIRED_JOBS; + + const focusedFamily = result.families.find((family) => family.id === "focused-e2e"); + expect(focusedFamily).toEqual( expect.objectContaining({ - id: "focused-e2e", matchedFiles: [changedFile], - requiredJobs: HERMES_MANAGED_POLICY_JOBS, + requiredJobs: expectedFocusedJobs, }), ); - expect(riskPlanRequiredJobIds(result)).toEqual( - expect.arrayContaining(HERMES_MANAGED_POLICY_JOBS), - ); + expect(riskPlanRequiredJobIds(result)).toEqual(expectedRequiredJobs); }); it("does not select managed-policy E2E for an unrelated Hermes runtime file (#8008)", () => { const result = plan("agents/hermes/runtime-version.py"); expect(result.families).not.toContainEqual(expect.objectContaining({ id: "focused-e2e" })); - expect(riskPlanRequiredJobIds(result)).toEqual(["full-e2e", "hermes-e2e", "security-posture"]); + expect(riskPlanRequiredJobIds(result)).toEqual([ + "full-e2e", + "hermes-e2e", + "hermes-inference-switch", + "security-posture", + ]); }); + it("combines CLI adapter and managed-policy E2E for the Hermes wrapper (#8011)", () => { + const result = plan("agents/hermes/hermes-wrapper.py"); + + expect(result.families).toContainEqual( + expect.objectContaining({ + id: "focused-e2e", + matchedFiles: ["agents/hermes/hermes-wrapper.py"], + requiredJobs: HERMES_WRAPPER_FOCUSED_JOBS, + }), + ); + expect(riskPlanRequiredJobIds(result)).toEqual(HERMES_WRAPPER_REQUIRED_JOBS); + }); it("leaves E2E support-only changes in the fast e2e-support project (#7921)", () => { const changedFiles = ["test/e2e/support/workflow-plan.test.ts"]; const focusedE2eJobs = focusedE2eJobsForChangedFiles(changedFiles); @@ -543,7 +618,12 @@ describe("deterministic PR risk plan", () => { expect(result.families.map((family) => family.id)).toContain("sandbox-boundary"); expect(riskPlanRequiredJobIds(result)).toEqual( - expect.arrayContaining(["full-e2e", "hermes-e2e", "security-posture"]), + expect.arrayContaining([ + "full-e2e", + "hermes-e2e", + "hermes-inference-switch", + "security-posture", + ]), ); }); diff --git a/tools/advisors/risk-plan.mts b/tools/advisors/risk-plan.mts index 9f658ea4a5e..c07bbac2a26 100644 --- a/tools/advisors/risk-plan.mts +++ b/tools/advisors/risk-plan.mts @@ -3,7 +3,7 @@ import { createHash } from "node:crypto"; -export const RISK_PLAN_VERSION = 11 as const; +export const RISK_PLAN_VERSION = 12 as const; export const PR_E2E_TYPED_TARGET_IDS = [ "ubuntu-repo-cloud-langchain-deepagents-code", @@ -25,6 +25,12 @@ const MANAGED_STARTUP_E2E_JOB_IDS = [ "issue-4462-scope-upgrade-approval", "openclaw-inference-switch", ] as const; +const HERMES_CLI_ADAPTER_E2E_JOB_IDS = ["channels-stop-start", "mcp-bridge"] as const; +const HERMES_CLI_ADAPTER_RUNTIME_FILES = new Set([ + "agents/hermes/hermes-cli-adapter-v1.json", + "agents/hermes/hermes-wrapper.py", + "agents/hermes/validate-cli-adapter.py", +]); const HERMES_MANAGED_POLICY_E2E_JOB_IDS = [ "bedrock-runtime-compatible-anthropic", "channels-stop-start", @@ -192,6 +198,11 @@ export function focusedPrE2eJobsForChangedFiles( isRuntimeRelevant(file), ), ); + const hermesCliAdapterFiles = stableUnique( + changedFiles.filter( + (file) => HERMES_CLI_ADAPTER_RUNTIME_FILES.has(file) && isRuntimeRelevant(file), + ), + ); const hermesManagedPolicyFiles = stableUnique( changedFiles.filter( (file) => @@ -201,6 +212,10 @@ export function focusedPrE2eJobsForChangedFiles( ); return [ ...MANAGED_STARTUP_E2E_JOB_IDS.map((id) => ({ id, matchedFiles: managedStartupFiles })), + ...HERMES_CLI_ADAPTER_E2E_JOB_IDS.map((id) => ({ + id, + matchedFiles: hermesCliAdapterFiles, + })), ...HERMES_MANAGED_POLICY_E2E_JOB_IDS.map((id) => ({ id, matchedFiles: hermesManagedPolicyFiles, @@ -372,9 +387,10 @@ export const RISK_RULES: readonly RiskRule[] = [ summary: "Sandbox blueprint and agent-runtime changes must preserve equivalent isolation and readiness across supported agents.", tier: 3, - requiredJobs: ["full-e2e", "hermes-e2e", "security-posture"], + requiredJobs: ["full-e2e", "hermes-e2e", "hermes-inference-switch", "security-posture"], invariants: [ "OpenClaw and Hermes both reach readiness through the changed sandbox boundary", + "the Hermes runtime preserves provider and model selection across managed inference route changes", "the sandbox retains its required security posture and isolation controls", "blueprint state agrees with the runtime observed by both supported agents", ], diff --git a/tools/pr-review-advisor/README.md b/tools/pr-review-advisor/README.md index d187bed00b4..ba65f84dac5 100644 --- a/tools/pr-review-advisor/README.md +++ b/tools/pr-review-advisor/README.md @@ -112,7 +112,7 @@ Authors and coding agents should follow the shared [PR CI and Review Follow-Up]( job that the model omits or downgrades. The PR E2E controller separately dispatches every listed job without consuming the advisor's normalized result. -Risk plan version 10 maps runtime changes from these paths to the `focused-e2e` family: +Risk plan version 12 maps runtime changes from these paths to the `focused-e2e` family: - `src/lib/onboard/managed-startup/**`. - `src/lib/onboard/sandbox-create-launch.ts`. @@ -124,6 +124,17 @@ Each match selects these focused E2E jobs: - `issue-4462-scope-upgrade-approval`. - `openclaw-inference-switch`. +The same risk plan maps these Hermes CLI adapter paths to `focused-e2e`: + +- `agents/hermes/hermes-cli-adapter-v1.json`. +- `agents/hermes/hermes-wrapper.py`. +- `agents/hermes/validate-cli-adapter.py`. + +Each Hermes CLI adapter match selects these focused E2E jobs: + +- `channels-stop-start`. +- `mcp-bridge`. + ## Required secret Configure this repository secret for review analysis: From 34af730876508d272c1dbd15a3755a35a0815cab Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Mon, 3 Aug 2026 02:10:55 -0700 Subject: [PATCH 2/3] test(e2e): update risk plan compatibility digest Signed-off-by: Carlos Villela --- test/e2e/support/e2e-cross-runtime-compatibility.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/support/e2e-cross-runtime-compatibility.test.ts b/test/e2e/support/e2e-cross-runtime-compatibility.test.ts index 8bff4bd3349..2a9e563a53f 100644 --- a/test/e2e/support/e2e-cross-runtime-compatibility.test.ts +++ b/test/e2e/support/e2e-cross-runtime-compatibility.test.ts @@ -45,7 +45,7 @@ describe("cross-runtime foundation compatibility", () => { ]; expect(digestOutput(cases.map(buildRiskPlan))).toBe( - "80ba8dfc73d0c7568d77338f0061a205a4414858749b33a7a7af6b4b670be8d7", + "b9f2f00f87b7a18caac504048c72eea224c10621690d2cf66549733487d3d342", ); }); }); From cc3f505dc941b3dc3c26578724b413491e014bb9 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 3 Aug 2026 10:32:35 -0400 Subject: [PATCH 3/3] test(e2e): clarify Hermes risk plan semantics Signed-off-by: Julie Yaunches --- test/e2e/README.md | 4 ++-- tools/advisors/risk-plan.mts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/e2e/README.md b/test/e2e/README.md index 492619fa760..32f34395ffb 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -625,8 +625,8 @@ configuration, and preparation and upload actions. These cross-cutting changes keep the broad three-job floor. Changes to the Hermes CLI wrapper, adapter manifest, or adapter validator also select `channels-stop-start` and `mcp-bridge`. Both jobs include the Hermes -shard. The shards exercise the adapter during messaging-channel disable/re-enable -and managed MCP add/restart/remove operations. +shard. The Hermes shards exercise the wrapper during `channels stop` and +`channels start`, and the adapter during `mcp add`, `mcp restart`, and `mcp remove`. Repository-root `Dockerfile` changes additionally select `full-e2e` alongside the platform-install `cloud-onboard` floor so OpenClaw final-image changes run through cold onboarding and a real first turn. diff --git a/tools/advisors/risk-plan.mts b/tools/advisors/risk-plan.mts index c07bbac2a26..1727be449c1 100644 --- a/tools/advisors/risk-plan.mts +++ b/tools/advisors/risk-plan.mts @@ -390,7 +390,7 @@ export const RISK_RULES: readonly RiskRule[] = [ requiredJobs: ["full-e2e", "hermes-e2e", "hermes-inference-switch", "security-posture"], invariants: [ "OpenClaw and Hermes both reach readiness through the changed sandbox boundary", - "the Hermes runtime preserves provider and model selection across managed inference route changes", + "the Hermes runtime and managed inference route agree on the selected provider and model after each route change", "the sandbox retains its required security posture and isolation controls", "blueprint state agrees with the runtime observed by both supported agents", ],