From f6b34dbe2df6bc5dc5c80935423a2e22969d984f Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 18 May 2026 18:03:14 -0400 Subject: [PATCH 01/12] test(e2e): add openclaw plugin EXDEV guard Adds a failing E2E test that demonstrates the bug tracked by #3513. Until the fix lands, the regression-e2e openclaw-plugin-runtime-exdev-e2e job will fail. This is intentional: the failing test is the executable acceptance criterion. Related: #3513 --- .github/workflows/regression-e2e.yaml | 43 ++++- .../e2e/test-openclaw-plugin-runtime-exdev.sh | 156 ++++++++++++++++++ 2 files changed, 198 insertions(+), 1 deletion(-) create mode 100755 test/e2e/test-openclaw-plugin-runtime-exdev.sh diff --git a/.github/workflows/regression-e2e.yaml b/.github/workflows/regression-e2e.yaml index 43126e85bf9..d6340d124e8 100644 --- a/.github/workflows/regression-e2e.yaml +++ b/.github/workflows/regression-e2e.yaml @@ -21,7 +21,7 @@ on: jobs: description: >- Comma-separated regression job names to run (empty = all). - Valid: dashboard-remote-bind-e2e,gateway-health-honest-e2e,gateway-drift-preflight-e2e,openshell-version-pin-e2e,onboard-inference-smoke-e2e,model-router-provider-routed-inference-e2e + Valid: dashboard-remote-bind-e2e,gateway-health-honest-e2e,gateway-drift-preflight-e2e,openshell-version-pin-e2e,onboard-inference-smoke-e2e,model-router-provider-routed-inference-e2e,openclaw-plugin-runtime-exdev-e2e required: false type: string default: "" @@ -50,6 +50,7 @@ jobs: openshell_version_pin: ${{ steps.select.outputs.openshell_version_pin }} onboard_inference_smoke: ${{ steps.select.outputs.onboard_inference_smoke }} model_router_provider_routed_inference: ${{ steps.select.outputs.model_router_provider_routed_inference }} + openclaw_plugin_runtime_exdev: ${{ steps.select.outputs.openclaw_plugin_runtime_exdev }} steps: - id: select env: @@ -101,6 +102,12 @@ jobs: echo "model_router_provider_routed_inference=false" >> "$GITHUB_OUTPUT" fi + if [ -z "$normalized" ] || includes_job "openclaw-plugin-runtime-exdev-e2e"; then + echo "openclaw_plugin_runtime_exdev=true" >> "$GITHUB_OUTPUT" + else + echo "openclaw_plugin_runtime_exdev=false" >> "$GITHUB_OUTPUT" + fi + dashboard-remote-bind-e2e: needs: select_regression_jobs if: >- @@ -290,3 +297,37 @@ jobs: /tmp/nemoclaw-e2e-model-router-health.log /tmp/nemoclaw-e2e-model-router-response.log if-no-files-found: ignore + + # ── OpenClaw plugin runtime-deps EXDEV E2E ───────────────────── + # Coverage guard for #3513 / #3127. On Ubuntu/OpenShell sandbox layouts + # where /tmp and /sandbox can live on different filesystems, OpenClaw's + # first CLI bootstrap must not fail plugin runtime dependency installation + # with EXDEV cross-device rename errors. + openclaw-plugin-runtime-exdev-e2e: + needs: select_regression_jobs + if: >- + github.repository == 'NVIDIA/NemoClaw' && + needs.select_regression_jobs.outputs.openclaw_plugin_runtime_exdev == 'true' + runs-on: ubuntu-latest + timeout-minutes: 45 + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Run OpenClaw plugin runtime-deps EXDEV E2E test + env: + NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }} + NEMOCLAW_NON_INTERACTIVE: "1" + NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1" + run: bash test/e2e/test-openclaw-plugin-runtime-exdev.sh + + - name: Upload OpenClaw plugin runtime-deps EXDEV logs on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: openclaw-plugin-runtime-exdev-logs + path: | + /tmp/nemoclaw-e2e-openclaw-plugin-exdev-onboard.log + /tmp/nemoclaw-e2e-openclaw-plugin-exdev-agent.log + /tmp/nemoclaw-e2e-openclaw-plugin-exdev-df.log + if-no-files-found: ignore diff --git a/test/e2e/test-openclaw-plugin-runtime-exdev.sh b/test/e2e/test-openclaw-plugin-runtime-exdev.sh new file mode 100755 index 00000000000..1811dc26241 --- /dev/null +++ b/test/e2e/test-openclaw-plugin-runtime-exdev.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Coverage guard for #3513 / #3127 — a fresh sandbox must be able to run the +# first OpenClaw CLI invocation without bundled plugin runtime-deps failing on +# EXDEV cross-device rename. + +set -uo pipefail + +PASS=0 +FAIL=0 +TOTAL=0 + +pass() { + ((PASS++)) + ((TOTAL++)) + echo " OK: $1" +} +fail() { + ((FAIL++)) + ((TOTAL++)) + echo " ERROR: $1" +} +section() { + echo "" + printf '\033[1;36m=== %s ===\033[0m\n' "$1" +} +info() { printf '\033[1;34m [info]\033[0m %s\n' "$1"; } + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO="$(cd "${SCRIPT_DIR}/../.." && pwd)" +SANDBOX_NAME="${NEMOCLAW_SANDBOX_NAME:-e2e-openclaw-plugin-exdev}" +ONBOARD_LOG="${E2E_OPENCLAW_PLUGIN_EXDEV_ONBOARD_LOG:-/tmp/nemoclaw-e2e-openclaw-plugin-exdev-onboard.log}" +AGENT_LOG="${E2E_OPENCLAW_PLUGIN_EXDEV_AGENT_LOG:-/tmp/nemoclaw-e2e-openclaw-plugin-exdev-agent.log}" +DF_LOG="${E2E_OPENCLAW_PLUGIN_EXDEV_DF_LOG:-/tmp/nemoclaw-e2e-openclaw-plugin-exdev-df.log}" +TIMEOUT_CMD="${TIMEOUT_CMD:-timeout}" + +# shellcheck source=test/e2e/lib/install-path-refresh.sh +. "${SCRIPT_DIR}/lib/install-path-refresh.sh" +# shellcheck source=test/e2e/lib/sandbox-teardown.sh +. "${SCRIPT_DIR}/lib/sandbox-teardown.sh" +register_sandbox_for_teardown "$SANDBOX_NAME" + +redact_file() { + local file="$1" + [ -f "$file" ] || return 0 + python3 - "$file" <<'PY' +import os, sys +path = sys.argv[1] +secrets = [os.environ.get("NVIDIA_API_KEY", ""), os.environ.get("NEMOCLAW_PROVIDER_KEY", "")] +text = open(path, "r", errors="replace").read() +for secret in filter(None, secrets): + text = text.replace(secret, "") +open(path, "w").write(text) +PY +} + +cleanup_logs() { + redact_file "$ONBOARD_LOG" + redact_file "$AGENT_LOG" + redact_file "$DF_LOG" +} +trap cleanup_logs EXIT + +section "Prerequisites" +if docker info >/dev/null 2>&1; then + pass "Docker is running" +else + fail "Docker is not running" + exit 1 +fi + +if [ -n "${NVIDIA_API_KEY:-}" ] && [[ "${NVIDIA_API_KEY}" == nvapi-* ]]; then + pass "NVIDIA_API_KEY is set" +else + fail "NVIDIA_API_KEY is required and must start with nvapi-" + exit 1 +fi + +section "Install NemoClaw from checkout" +if ! command -v nemoclaw >/dev/null 2>&1; then + NEMOCLAW_NON_INTERACTIVE=1 \ + NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE=1 \ + bash "${REPO}/install.sh" --non-interactive --yes-i-accept-third-party-software >"$ONBOARD_LOG" 2>&1 || true + nemoclaw_refresh_install_env +fi + +if command -v nemoclaw >/dev/null 2>&1; then + pass "nemoclaw is available: $(nemoclaw --version 2>/dev/null || echo unknown)" +else + fail "nemoclaw not found after install" + exit 1 +fi + +section "Fresh sandbox onboard" +rm -f "$HOME/.nemoclaw/onboard.lock" 2>/dev/null || true +nemoclaw "$SANDBOX_NAME" destroy --yes >/dev/null 2>&1 || true + +env \ + NEMOCLAW_PROVIDER_KEY="$NVIDIA_API_KEY" \ + NEMOCLAW_SANDBOX_NAME="$SANDBOX_NAME" \ + NEMOCLAW_NON_INTERACTIVE=1 \ + NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE=1 \ + NEMOCLAW_POLICY_TIER="open" \ + NEMOCLAW_PROVIDER="build" \ + NVIDIA_API_KEY="$NVIDIA_API_KEY" \ + "$TIMEOUT_CMD" 1500 nemoclaw onboard --fresh --non-interactive --yes-i-accept-third-party-software \ + >"$ONBOARD_LOG" 2>&1 +onboard_rc=$? +redact_file "$ONBOARD_LOG" +if [ "$onboard_rc" -eq 0 ]; then + pass "fresh sandbox onboard completed" +else + fail "fresh sandbox onboard failed (exit ${onboard_rc}); see ${ONBOARD_LOG}" + exit 1 +fi + +section "Filesystem layout evidence" +openshell sandbox exec --name "$SANDBOX_NAME" -- sh -lc 'df -PT / /tmp /sandbox /sandbox/.openclaw/plugin-runtime-deps 2>&1' \ + >"$DF_LOG" 2>&1 || true +redact_file "$DF_LOG" +info "Filesystem layout captured in ${DF_LOG}" + +section "First OpenClaw agent bootstrap" +agent_rc=0 +session_id="plugin-exdev-$(date +%s)" +remote_cmd="rm -f /sandbox/.openclaw/agents/main/sessions/${session_id}.jsonl.lock /sandbox/.openclaw/agents/main/sessions/${session_id}.trajectory.jsonl 2>/dev/null || true; openclaw agent --agent main --json --session-id '${session_id}' -m 'Reply with exactly one word: PONG'" +"$TIMEOUT_CMD" 420 openshell sandbox exec --name "$SANDBOX_NAME" -- sh -lc "$remote_cmd" \ + >"$AGENT_LOG" 2>&1 || agent_rc=$? +redact_file "$AGENT_LOG" + +if grep -qiE 'EXDEV: cross-device link not permitted|failed to install bundled runtime deps|PluginLoadFailureError' "$AGENT_LOG"; then + fail "OpenClaw plugin runtime deps hit #3513 EXDEV failure during first agent bootstrap" + info "Agent log excerpt: $(grep -iE 'EXDEV|failed to install bundled runtime deps|PluginLoadFailureError' "$AGENT_LOG" | head -5 | tr '\n' ' ')" + exit 1 +fi + +if [ "$agent_rc" -ne 0 ]; then + fail "openclaw agent exited ${agent_rc}; see ${AGENT_LOG}" + exit 1 +fi + +if grep -qi 'PONG' "$AGENT_LOG"; then + pass "openclaw agent completed without plugin runtime-deps EXDEV and returned a response" +else + fail "openclaw agent exited 0 but expected response token was missing; see ${AGENT_LOG}" + exit 1 +fi + +section "Summary" +if [ "$FAIL" -eq 0 ]; then + pass "OpenClaw plugin runtime-deps EXDEV guard passed" + exit 0 +fi +exit 1 From 4b736c1ecfb3911cdd1fd44f8b1e24ea457975b7 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 18 May 2026 18:20:29 -0400 Subject: [PATCH 02/12] test(e2e): force cross-device plugin staging --- test/e2e/test-openclaw-plugin-runtime-exdev.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/e2e/test-openclaw-plugin-runtime-exdev.sh b/test/e2e/test-openclaw-plugin-runtime-exdev.sh index 1811dc26241..392b0f284ac 100755 --- a/test/e2e/test-openclaw-plugin-runtime-exdev.sh +++ b/test/e2e/test-openclaw-plugin-runtime-exdev.sh @@ -117,15 +117,19 @@ else fi section "Filesystem layout evidence" -openshell sandbox exec --name "$SANDBOX_NAME" -- sh -lc 'df -PT / /tmp /sandbox /sandbox/.openclaw/plugin-runtime-deps 2>&1' \ +openshell sandbox exec --name "$SANDBOX_NAME" -- sh -lc 'df -PT / /tmp /dev/shm /sandbox /sandbox/.openclaw/plugin-runtime-deps 2>&1' \ >"$DF_LOG" 2>&1 || true redact_file "$DF_LOG" info "Filesystem layout captured in ${DF_LOG}" -section "First OpenClaw agent bootstrap" +section "First OpenClaw agent bootstrap with cross-device staging" agent_rc=0 session_id="plugin-exdev-$(date +%s)" -remote_cmd="rm -f /sandbox/.openclaw/agents/main/sessions/${session_id}.jsonl.lock /sandbox/.openclaw/agents/main/sessions/${session_id}.trajectory.jsonl 2>/dev/null || true; openclaw agent --agent main --json --session-id '${session_id}' -m 'Reply with exactly one word: PONG'" +# Force OpenClaw's bundled runtime-deps staging dir onto /dev/shm (tmpfs) and +# clear any deps preinstalled by gateway startup. On unfixed OpenClaw builds, +# the installer attempts fs.rename(stagedDir, targetDir), which fails with +# EXDEV when stagedDir is on /dev/shm and targetDir is under /sandbox. +remote_cmd="rm -rf /sandbox/.openclaw/plugin-runtime-deps/openclaw-* 2>/dev/null || true; rm -f /sandbox/.openclaw/agents/main/sessions/${session_id}.jsonl.lock /sandbox/.openclaw/agents/main/sessions/${session_id}.trajectory.jsonl 2>/dev/null || true; TMPDIR=/dev/shm openclaw agent --agent main --json --session-id '${session_id}' -m 'Reply with exactly one word: PONG'" "$TIMEOUT_CMD" 420 openshell sandbox exec --name "$SANDBOX_NAME" -- sh -lc "$remote_cmd" \ >"$AGENT_LOG" 2>&1 || agent_rc=$? redact_file "$AGENT_LOG" @@ -142,7 +146,7 @@ if [ "$agent_rc" -ne 0 ]; then fi if grep -qi 'PONG' "$AGENT_LOG"; then - pass "openclaw agent completed without plugin runtime-deps EXDEV and returned a response" + pass "openclaw agent completed without plugin runtime-deps EXDEV despite cross-device staging" else fail "openclaw agent exited 0 but expected response token was missing; see ${AGENT_LOG}" exit 1 From 3ae78584cc8c10893c5f0a12c20c825757c864ea Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 18 May 2026 18:32:46 -0400 Subject: [PATCH 03/12] test(e2e): prepare tmpfs fallback dir --- test/e2e/test-openclaw-plugin-runtime-exdev.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/test-openclaw-plugin-runtime-exdev.sh b/test/e2e/test-openclaw-plugin-runtime-exdev.sh index 392b0f284ac..aa3830d5c55 100755 --- a/test/e2e/test-openclaw-plugin-runtime-exdev.sh +++ b/test/e2e/test-openclaw-plugin-runtime-exdev.sh @@ -129,7 +129,7 @@ session_id="plugin-exdev-$(date +%s)" # clear any deps preinstalled by gateway startup. On unfixed OpenClaw builds, # the installer attempts fs.rename(stagedDir, targetDir), which fails with # EXDEV when stagedDir is on /dev/shm and targetDir is under /sandbox. -remote_cmd="rm -rf /sandbox/.openclaw/plugin-runtime-deps/openclaw-* 2>/dev/null || true; rm -f /sandbox/.openclaw/agents/main/sessions/${session_id}.jsonl.lock /sandbox/.openclaw/agents/main/sessions/${session_id}.trajectory.jsonl 2>/dev/null || true; TMPDIR=/dev/shm openclaw agent --agent main --json --session-id '${session_id}' -m 'Reply with exactly one word: PONG'" +remote_cmd="rm -rf /sandbox/.openclaw/plugin-runtime-deps/openclaw-* 2>/dev/null || true; rm -f /sandbox/.openclaw/agents/main/sessions/${session_id}.jsonl.lock /sandbox/.openclaw/agents/main/sessions/${session_id}.trajectory.jsonl 2>/dev/null || true; mkdir -p /dev/shm/openclaw-$(id -u) && chmod 700 /dev/shm/openclaw-$(id -u) && TMPDIR=/dev/shm openclaw agent --agent main --json --session-id '${session_id}' -m 'Reply with exactly one word: PONG'" "$TIMEOUT_CMD" 420 openshell sandbox exec --name "$SANDBOX_NAME" -- sh -lc "$remote_cmd" \ >"$AGENT_LOG" 2>&1 || agent_rc=$? redact_file "$AGENT_LOG" From 0ec0e18a5d55a1523b6ff6825fb5cb1824a78d42 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 18 May 2026 18:46:57 -0400 Subject: [PATCH 04/12] test(e2e): avoid tmpfs fallback setup --- test/e2e/test-openclaw-plugin-runtime-exdev.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/test-openclaw-plugin-runtime-exdev.sh b/test/e2e/test-openclaw-plugin-runtime-exdev.sh index aa3830d5c55..d3b9f362336 100755 --- a/test/e2e/test-openclaw-plugin-runtime-exdev.sh +++ b/test/e2e/test-openclaw-plugin-runtime-exdev.sh @@ -129,7 +129,7 @@ session_id="plugin-exdev-$(date +%s)" # clear any deps preinstalled by gateway startup. On unfixed OpenClaw builds, # the installer attempts fs.rename(stagedDir, targetDir), which fails with # EXDEV when stagedDir is on /dev/shm and targetDir is under /sandbox. -remote_cmd="rm -rf /sandbox/.openclaw/plugin-runtime-deps/openclaw-* 2>/dev/null || true; rm -f /sandbox/.openclaw/agents/main/sessions/${session_id}.jsonl.lock /sandbox/.openclaw/agents/main/sessions/${session_id}.trajectory.jsonl 2>/dev/null || true; mkdir -p /dev/shm/openclaw-$(id -u) && chmod 700 /dev/shm/openclaw-$(id -u) && TMPDIR=/dev/shm openclaw agent --agent main --json --session-id '${session_id}' -m 'Reply with exactly one word: PONG'" +remote_cmd="rm -rf /sandbox/.openclaw/plugin-runtime-deps/openclaw-* 2>/dev/null || true; rm -f /sandbox/.openclaw/agents/main/sessions/${session_id}.jsonl.lock /sandbox/.openclaw/agents/main/sessions/${session_id}.trajectory.jsonl 2>/dev/null || true; TMPDIR=/dev/shm XDG_RUNTIME_DIR= openclaw agent --agent main --json --session-id '${session_id}' -m 'Reply with exactly one word: PONG'" "$TIMEOUT_CMD" 420 openshell sandbox exec --name "$SANDBOX_NAME" -- sh -lc "$remote_cmd" \ >"$AGENT_LOG" 2>&1 || agent_rc=$? redact_file "$AGENT_LOG" From df4d171d4070fccb3339ac3e8c5e89418eed9360 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 18 May 2026 19:00:51 -0400 Subject: [PATCH 05/12] test(e2e): preserve openclaw fallback temp --- test/e2e/test-openclaw-plugin-runtime-exdev.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/test-openclaw-plugin-runtime-exdev.sh b/test/e2e/test-openclaw-plugin-runtime-exdev.sh index d3b9f362336..cf2839e8643 100755 --- a/test/e2e/test-openclaw-plugin-runtime-exdev.sh +++ b/test/e2e/test-openclaw-plugin-runtime-exdev.sh @@ -129,7 +129,7 @@ session_id="plugin-exdev-$(date +%s)" # clear any deps preinstalled by gateway startup. On unfixed OpenClaw builds, # the installer attempts fs.rename(stagedDir, targetDir), which fails with # EXDEV when stagedDir is on /dev/shm and targetDir is under /sandbox. -remote_cmd="rm -rf /sandbox/.openclaw/plugin-runtime-deps/openclaw-* 2>/dev/null || true; rm -f /sandbox/.openclaw/agents/main/sessions/${session_id}.jsonl.lock /sandbox/.openclaw/agents/main/sessions/${session_id}.trajectory.jsonl 2>/dev/null || true; TMPDIR=/dev/shm XDG_RUNTIME_DIR= openclaw agent --agent main --json --session-id '${session_id}' -m 'Reply with exactly one word: PONG'" +remote_cmd="rm -rf /sandbox/.openclaw/plugin-runtime-deps/openclaw-* 2>/dev/null || true; rm -f /sandbox/.openclaw/agents/main/sessions/${session_id}.jsonl.lock /sandbox/.openclaw/agents/main/sessions/${session_id}.trajectory.jsonl 2>/dev/null || true; TMPDIR=/dev/shm NEMOCLAW_TMPDIR=/tmp openclaw agent --agent main --json --session-id '${session_id}' -m 'Reply with exactly one word: PONG'" "$TIMEOUT_CMD" 420 openshell sandbox exec --name "$SANDBOX_NAME" -- sh -lc "$remote_cmd" \ >"$AGENT_LOG" 2>&1 || agent_rc=$? redact_file "$AGENT_LOG" From a3b736bf273432fbf2001e52208903eecd7def8f Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 18 May 2026 19:40:10 -0400 Subject: [PATCH 06/12] test(e2e): target plugin deps rename helper --- .../e2e/test-openclaw-plugin-runtime-exdev.sh | 56 ++++++++++++++----- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/test/e2e/test-openclaw-plugin-runtime-exdev.sh b/test/e2e/test-openclaw-plugin-runtime-exdev.sh index cf2839e8643..7188aa05b9d 100755 --- a/test/e2e/test-openclaw-plugin-runtime-exdev.sh +++ b/test/e2e/test-openclaw-plugin-runtime-exdev.sh @@ -122,33 +122,59 @@ openshell sandbox exec --name "$SANDBOX_NAME" -- sh -lc 'df -PT / /tmp /dev/shm redact_file "$DF_LOG" info "Filesystem layout captured in ${DF_LOG}" -section "First OpenClaw agent bootstrap with cross-device staging" +section "Bundled plugin runtime-deps cross-device replacement" agent_rc=0 -session_id="plugin-exdev-$(date +%s)" -# Force OpenClaw's bundled runtime-deps staging dir onto /dev/shm (tmpfs) and -# clear any deps preinstalled by gateway startup. On unfixed OpenClaw builds, -# the installer attempts fs.rename(stagedDir, targetDir), which fails with -# EXDEV when stagedDir is on /dev/shm and targetDir is under /sandbox. -remote_cmd="rm -rf /sandbox/.openclaw/plugin-runtime-deps/openclaw-* 2>/dev/null || true; rm -f /sandbox/.openclaw/agents/main/sessions/${session_id}.jsonl.lock /sandbox/.openclaw/agents/main/sessions/${session_id}.trajectory.jsonl 2>/dev/null || true; TMPDIR=/dev/shm NEMOCLAW_TMPDIR=/tmp openclaw agent --agent main --json --session-id '${session_id}' -m 'Reply with exactly one word: PONG'" -"$TIMEOUT_CMD" 420 openshell sandbox exec --name "$SANDBOX_NAME" -- sh -lc "$remote_cmd" \ +# Reproduce the precise #3513 failure mode without depending on OpenClaw's +# broader CLI temp/log initialization: the vulnerable helper copies dependency +# contents into a staging dir adjacent to the source and then renameSyncs that +# staged node_modules dir into the final plugin-runtime-deps target. When source +# is on tmpfs (/dev/shm) and target is under /sandbox, unfixed code throws EXDEV. +remote_cmd=$(cat <<'REMOTE' +set -eu +rm -rf /sandbox/.openclaw/plugin-runtime-deps/exdev-guard 2>/dev/null || true +rm -rf /dev/shm/nemoclaw-exdev-source 2>/dev/null || true +mkdir -p /dev/shm/nemoclaw-exdev-source +printf 'ok\n' >/dev/shm/nemoclaw-exdev-source/package.txt +node --input-type=module - <<'NODE' +import fs from 'node:fs'; +import path from 'node:path'; +function replaceNodeModulesDir(targetDir, sourceDir) { + const parentDir = path.dirname(sourceDir); + fs.mkdirSync(path.dirname(targetDir), { recursive: true }); + const tempDir = fs.mkdtempSync(path.join(parentDir, '.openclaw-runtime-deps-copy-')); + const stagedDir = path.join(tempDir, 'node_modules'); + try { + fs.cpSync(sourceDir, stagedDir, { recursive: true }); + fs.rmSync(targetDir, { recursive: true, force: true }); + fs.renameSync(stagedDir, targetDir); + } finally { + try { fs.rmSync(tempDir, { recursive: true, force: true }); } catch {} + } +} +replaceNodeModulesDir('/sandbox/.openclaw/plugin-runtime-deps/exdev-guard/node_modules', '/dev/shm/nemoclaw-exdev-source'); +console.log('runtime deps replacement completed'); +NODE +REMOTE +) +"$TIMEOUT_CMD" 60 openshell sandbox exec --name "$SANDBOX_NAME" -- sh -lc "$remote_cmd" \ >"$AGENT_LOG" 2>&1 || agent_rc=$? redact_file "$AGENT_LOG" -if grep -qiE 'EXDEV: cross-device link not permitted|failed to install bundled runtime deps|PluginLoadFailureError' "$AGENT_LOG"; then - fail "OpenClaw plugin runtime deps hit #3513 EXDEV failure during first agent bootstrap" - info "Agent log excerpt: $(grep -iE 'EXDEV|failed to install bundled runtime deps|PluginLoadFailureError' "$AGENT_LOG" | head -5 | tr '\n' ' ')" +if grep -qiE 'EXDEV: cross-device link not permitted|cross-device link not permitted' "$AGENT_LOG"; then + fail "OpenClaw-style plugin runtime deps replacement hit #3513 EXDEV failure" + info "Runtime-deps log excerpt: $(grep -iE 'EXDEV|cross-device link not permitted' "$AGENT_LOG" | head -5 | tr '\n' ' ')" exit 1 fi if [ "$agent_rc" -ne 0 ]; then - fail "openclaw agent exited ${agent_rc}; see ${AGENT_LOG}" + fail "runtime deps replacement exited ${agent_rc}; see ${AGENT_LOG}" exit 1 fi -if grep -qi 'PONG' "$AGENT_LOG"; then - pass "openclaw agent completed without plugin runtime-deps EXDEV despite cross-device staging" +if grep -q 'runtime deps replacement completed' "$AGENT_LOG"; then + pass "OpenClaw-style plugin runtime-deps replacement completed across filesystems" else - fail "openclaw agent exited 0 but expected response token was missing; see ${AGENT_LOG}" + fail "runtime deps replacement exited 0 but success marker was missing; see ${AGENT_LOG}" exit 1 fi From cfe3d272f14a6ef30167e1dc26615088df000c55 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 18 May 2026 19:53:45 -0400 Subject: [PATCH 07/12] test(e2e): encode remote exdev script --- test/e2e/test-openclaw-plugin-runtime-exdev.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/e2e/test-openclaw-plugin-runtime-exdev.sh b/test/e2e/test-openclaw-plugin-runtime-exdev.sh index 7188aa05b9d..328ba15e9f0 100755 --- a/test/e2e/test-openclaw-plugin-runtime-exdev.sh +++ b/test/e2e/test-openclaw-plugin-runtime-exdev.sh @@ -129,7 +129,7 @@ agent_rc=0 # contents into a staging dir adjacent to the source and then renameSyncs that # staged node_modules dir into the final plugin-runtime-deps target. When source # is on tmpfs (/dev/shm) and target is under /sandbox, unfixed code throws EXDEV. -remote_cmd=$(cat <<'REMOTE' +remote_script_b64=$(cat <<'REMOTE' | base64 | tr -d '\n' set -eu rm -rf /sandbox/.openclaw/plugin-runtime-deps/exdev-guard 2>/dev/null || true rm -rf /dev/shm/nemoclaw-exdev-source 2>/dev/null || true @@ -156,6 +156,7 @@ console.log('runtime deps replacement completed'); NODE REMOTE ) +remote_cmd="printf '%s' '${remote_script_b64}' | base64 -d > /tmp/nemoclaw-exdev-guard.sh && sh /tmp/nemoclaw-exdev-guard.sh" "$TIMEOUT_CMD" 60 openshell sandbox exec --name "$SANDBOX_NAME" -- sh -lc "$remote_cmd" \ >"$AGENT_LOG" 2>&1 || agent_rc=$? redact_file "$AGENT_LOG" From 9574562e5941dc1b1edc960ce7b2b8ee02abcd41 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 18 May 2026 20:57:49 -0400 Subject: [PATCH 08/12] test(e2e): grant shm writes for exdev fixture --- .../e2e/test-openclaw-plugin-runtime-exdev.sh | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/test/e2e/test-openclaw-plugin-runtime-exdev.sh b/test/e2e/test-openclaw-plugin-runtime-exdev.sh index 328ba15e9f0..4bba565d86d 100755 --- a/test/e2e/test-openclaw-plugin-runtime-exdev.sh +++ b/test/e2e/test-openclaw-plugin-runtime-exdev.sh @@ -97,15 +97,29 @@ section "Fresh sandbox onboard" rm -f "$HOME/.nemoclaw/onboard.lock" 2>/dev/null || true nemoclaw "$SANDBOX_NAME" destroy --yes >/dev/null 2>&1 || true + +OPENCLAW_POLICY_SRC="${REPO}/agents/openclaw/policy-permissive.yaml" +EXDEV_POLICY="/tmp/nemoclaw-e2e-openclaw-plugin-exdev-policy.yaml" +python3 - "$OPENCLAW_POLICY_SRC" "$EXDEV_POLICY" <<'PY' +import sys +from pathlib import Path +src, dst = map(Path, sys.argv[1:]) +text = src.read_text() +needle = " read_write:\n - /tmp\n" +if needle not in text: + raise SystemExit("could not find read_write /tmp anchor in OpenClaw policy") +text = text.replace(needle, " read_write:\n - /tmp\n - /dev/shm\n", 1) +dst.write_text(text) +PY env \ NEMOCLAW_PROVIDER_KEY="$NVIDIA_API_KEY" \ NEMOCLAW_SANDBOX_NAME="$SANDBOX_NAME" \ NEMOCLAW_NON_INTERACTIVE=1 \ NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE=1 \ - NEMOCLAW_POLICY_TIER="open" \ + NEMOCLAW_POLICY_MODE="skip" \ NEMOCLAW_PROVIDER="build" \ NVIDIA_API_KEY="$NVIDIA_API_KEY" \ - "$TIMEOUT_CMD" 1500 nemoclaw onboard --fresh --non-interactive --yes-i-accept-third-party-software \ + "$TIMEOUT_CMD" 1500 nemoclaw onboard --fresh --non-interactive --yes-i-accept-third-party-software --agent openclaw --from "$REPO/Dockerfile" \ >"$ONBOARD_LOG" 2>&1 onboard_rc=$? redact_file "$ONBOARD_LOG" @@ -122,6 +136,14 @@ openshell sandbox exec --name "$SANDBOX_NAME" -- sh -lc 'df -PT / /tmp /dev/shm redact_file "$DF_LOG" info "Filesystem layout captured in ${DF_LOG}" +section "Apply EXDEV filesystem policy" +if openshell policy set --policy "$EXDEV_POLICY" --wait "$SANDBOX_NAME" >>"$ONBOARD_LOG" 2>&1; then + pass "sandbox policy permits /dev/shm writes for EXDEV fixture" +else + fail "failed to apply EXDEV filesystem policy; see ${ONBOARD_LOG}" + exit 1 +fi + section "Bundled plugin runtime-deps cross-device replacement" agent_rc=0 # Reproduce the precise #3513 failure mode without depending on OpenClaw's From f2e4ff273273d006628425b03a443cd1e7608578 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 18 May 2026 21:40:23 -0400 Subject: [PATCH 09/12] test(e2e): bake shm policy into exdev sandbox --- .../e2e/test-openclaw-plugin-runtime-exdev.sh | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/test/e2e/test-openclaw-plugin-runtime-exdev.sh b/test/e2e/test-openclaw-plugin-runtime-exdev.sh index 4bba565d86d..4a5a7ed576d 100755 --- a/test/e2e/test-openclaw-plugin-runtime-exdev.sh +++ b/test/e2e/test-openclaw-plugin-runtime-exdev.sh @@ -99,17 +99,17 @@ nemoclaw "$SANDBOX_NAME" destroy --yes >/dev/null 2>&1 || true OPENCLAW_POLICY_SRC="${REPO}/agents/openclaw/policy-permissive.yaml" -EXDEV_POLICY="/tmp/nemoclaw-e2e-openclaw-plugin-exdev-policy.yaml" -python3 - "$OPENCLAW_POLICY_SRC" "$EXDEV_POLICY" <<'PY' +python3 - "$OPENCLAW_POLICY_SRC" <<'PY' import sys from pathlib import Path -src, dst = map(Path, sys.argv[1:]) -text = src.read_text() -needle = " read_write:\n - /tmp\n" -if needle not in text: - raise SystemExit("could not find read_write /tmp anchor in OpenClaw policy") -text = text.replace(needle, " read_write:\n - /tmp\n - /dev/shm\n", 1) -dst.write_text(text) +path = Path(sys.argv[1]) +text = path.read_text() +if " - /dev/shm\n" not in text: + needle = " read_write:\n - /tmp\n" + if needle not in text: + raise SystemExit("could not find read_write /tmp anchor in OpenClaw policy") + text = text.replace(needle, " read_write:\n - /tmp\n - /dev/shm\n", 1) + path.write_text(text) PY env \ NEMOCLAW_PROVIDER_KEY="$NVIDIA_API_KEY" \ @@ -136,14 +136,6 @@ openshell sandbox exec --name "$SANDBOX_NAME" -- sh -lc 'df -PT / /tmp /dev/shm redact_file "$DF_LOG" info "Filesystem layout captured in ${DF_LOG}" -section "Apply EXDEV filesystem policy" -if openshell policy set --policy "$EXDEV_POLICY" --wait "$SANDBOX_NAME" >>"$ONBOARD_LOG" 2>&1; then - pass "sandbox policy permits /dev/shm writes for EXDEV fixture" -else - fail "failed to apply EXDEV filesystem policy; see ${ONBOARD_LOG}" - exit 1 -fi - section "Bundled plugin runtime-deps cross-device replacement" agent_rc=0 # Reproduce the precise #3513 failure mode without depending on OpenClaw's From 02a45176e82960a1644c197e8d17be614e6cf15d Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Mon, 18 May 2026 22:16:14 -0400 Subject: [PATCH 10/12] test(e2e): widen dev shm sandbox policy --- .../e2e/test-openclaw-plugin-runtime-exdev.sh | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/test/e2e/test-openclaw-plugin-runtime-exdev.sh b/test/e2e/test-openclaw-plugin-runtime-exdev.sh index 4a5a7ed576d..77b249a4458 100755 --- a/test/e2e/test-openclaw-plugin-runtime-exdev.sh +++ b/test/e2e/test-openclaw-plugin-runtime-exdev.sh @@ -98,18 +98,26 @@ rm -f "$HOME/.nemoclaw/onboard.lock" 2>/dev/null || true nemoclaw "$SANDBOX_NAME" destroy --yes >/dev/null 2>&1 || true -OPENCLAW_POLICY_SRC="${REPO}/agents/openclaw/policy-permissive.yaml" -python3 - "$OPENCLAW_POLICY_SRC" <<'PY' +python3 - "${REPO}" <<'PY' import sys from pathlib import Path -path = Path(sys.argv[1]) -text = path.read_text() -if " - /dev/shm\n" not in text: +repo = Path(sys.argv[1]) +policy_paths = [ + repo / "agents/openclaw/policy-permissive.yaml", + repo / "nemoclaw-blueprint/policies/openclaw-sandbox.yaml", + repo / "nemoclaw-blueprint/policies/openclaw-sandbox-permissive.yaml", +] +for path in policy_paths: + text = path.read_text() needle = " read_write:\n - /tmp\n" if needle not in text: - raise SystemExit("could not find read_write /tmp anchor in OpenClaw policy") - text = text.replace(needle, " read_write:\n - /tmp\n - /dev/shm\n", 1) - path.write_text(text) + raise SystemExit(f"could not find read_write /tmp anchor in {path}") + additions = "" + for entry in ["/dev", "/dev/shm"]: + if f" - {entry}\n" not in text: + additions += f" - {entry}\n" + if additions: + path.write_text(text.replace(needle, needle + additions, 1)) PY env \ NEMOCLAW_PROVIDER_KEY="$NVIDIA_API_KEY" \ From 878667799a4a2da689572533a2fa92d410137723 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Tue, 19 May 2026 09:54:28 -0400 Subject: [PATCH 11/12] fix(e2e): update exdev guard parity metadata --- test/e2e/docs/parity-inventory.generated.json | 711 +++++++++++++----- test/e2e/docs/parity-map.yaml | 70 ++ .../e2e/test-openclaw-plugin-runtime-exdev.sh | 14 +- 3 files changed, 591 insertions(+), 204 deletions(-) diff --git a/test/e2e/docs/parity-inventory.generated.json b/test/e2e/docs/parity-inventory.generated.json index e60c90fb654..dce70a0efc0 100644 --- a/test/e2e/docs/parity-inventory.generated.json +++ b/test/e2e/docs/parity-inventory.generated.json @@ -7404,7 +7404,7 @@ "assertions": [ { "script": "test/e2e/test-messaging-providers.sh", - "line": 205, + "line": 247, "text": "NVIDIA_API_KEY not set", "polarity": "fail", "normalized_id": "nvidia.api.key.not.set", @@ -7412,7 +7412,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 208, + "line": 250, "text": "NVIDIA_API_KEY is set", "polarity": "pass", "normalized_id": "nvidia.api.key.is.set", @@ -7420,7 +7420,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 211, + "line": 253, "text": "Docker is not running", "polarity": "fail", "normalized_id": "docker.is.not.running", @@ -7428,7 +7428,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 214, + "line": 256, "text": "Docker is running", "polarity": "pass", "normalized_id": "docker.is.running", @@ -7436,7 +7436,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 248, + "line": 290, "text": "Pre-cleanup complete", "polarity": "pass", "normalized_id": "pre.cleanup.complete", @@ -7444,7 +7444,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 328, + "line": 370, "text": "Failed to append Slack policy to base sandbox policy", "polarity": "fail", "normalized_id": "failed.to.append.slack.policy.to.base.sandbox.policy", @@ -7452,7 +7452,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 331, + "line": 373, "text": "Slack network policy pre-merged into base policy", "polarity": "pass", "normalized_id": "slack.network.policy.pre.merged.into.base.policy", @@ -7460,7 +7460,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 336, + "line": 378, "text": "Cannot pre-merge Slack policy: missing base policy or preset file", "polarity": "fail", "normalized_id": "cannot.pre.merge.slack.policy.missing.base.policy.or.preset.file", @@ -7468,7 +7468,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 377, + "line": 419, "text": "M0: install.sh completed (exit 0)", "polarity": "pass", "normalized_id": "m0.install.sh.completed.exit.0", @@ -7476,7 +7476,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 379, + "line": 421, "text": "M0: install.sh failed (exit $install_exit)", "polarity": "fail", "normalized_id": "m0.install.sh.failed.exit.install.exit", @@ -7484,7 +7484,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 387, + "line": 429, "text": "openshell not found on PATH after install", "polarity": "fail", "normalized_id": "openshell.not.found.on.path.after.install", @@ -7492,7 +7492,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 390, + "line": 432, "text": "openshell installed ($(openshell --version 2>&1 || echo unknown))", "polarity": "pass", "normalized_id": "openshell.installed.openshell.version.2.1.echo.unknown", @@ -7500,7 +7500,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 393, + "line": 435, "text": "nemoclaw not found on PATH after install", "polarity": "fail", "normalized_id": "nemoclaw.not.found.on.path.after.install", @@ -7508,7 +7508,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 396, + "line": 438, "text": "nemoclaw installed at $(command -v nemoclaw)", "polarity": "pass", "normalized_id": "nemoclaw.installed.at.command.v.nemoclaw", @@ -7516,7 +7516,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 401, + "line": 443, "text": "M0b: Sandbox '$SANDBOX_NAME' is Ready", "polarity": "pass", "normalized_id": "m0b.sandbox.sandbox.name.is.ready", @@ -7524,7 +7524,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 403, + "line": 445, "text": "M0b: Sandbox '$SANDBOX_NAME' not Ready (list: ${sandbox_list:0:200})", "polarity": "fail", "normalized_id": "m0b.sandbox.sandbox.name.not.ready.list.sandbox.list.0.200", @@ -7532,7 +7532,119 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 409, + "line": 463, + "text": "M-WA0: channels add whatsapp registered QR-only channel", + "polarity": "pass", + "normalized_id": "m.wa0.channels.add.whatsapp.registered.qr.only.channel", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 465, + "text": "M-WA0: channels add whatsapp failed or did not register channel", + "polarity": "fail", + "normalized_id": "m.wa0.channels.add.whatsapp.failed.or.did.not.register.channel", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 471, + "text": "M-WA1: Unexpected WhatsApp bridge provider exists in gateway", + "polarity": "fail", + "normalized_id": "m.wa1.unexpected.whatsapp.bridge.provider.exists.in.gateway", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 473, + "text": "M-WA1: WhatsApp QR-only channel creates no bridge provider", + "polarity": "pass", + "normalized_id": "m.wa1.whatsapp.qr.only.channel.creates.no.bridge.provider", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 477, + "text": "M-WA2: registry.messagingChannels contains whatsapp after channel add", + "polarity": "pass", + "normalized_id": "m.wa2.registry.messagingchannels.contains.whatsapp.after.channel.add", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 479, + "text": "M-WA2: registry.messagingChannels missing whatsapp after channel add ($(registry_field messagingChannels))", + "polarity": "fail", + "normalized_id": "m.wa2.registry.messagingchannels.missing.whatsapp.after.channel.add.registry.field.messagingchannels", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 486, + "text": "M-WA3: WhatsApp policy preset applied before rebuild", + "polarity": "pass", + "normalized_id": "m.wa3.whatsapp.policy.preset.applied.before.rebuild", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 488, + "text": "M-WA3: WhatsApp policy preset missing expected endpoints before rebuild", + "polarity": "fail", + "normalized_id": "m.wa3.whatsapp.policy.preset.missing.expected.endpoints.before.rebuild", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 494, + "text": "M-WA4: Rebuild completed after WhatsApp channel add", + "polarity": "pass", + "normalized_id": "m.wa4.rebuild.completed.after.whatsapp.channel.add", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 496, + "text": "M-WA4: Rebuild failed after WhatsApp channel add", + "polarity": "fail", + "normalized_id": "m.wa4.rebuild.failed.after.whatsapp.channel.add", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 506, + "text": "M-WA5: WhatsApp policy preset survived rebuild with Node binary scope", + "polarity": "pass", + "normalized_id": "m.wa5.whatsapp.policy.preset.survived.rebuild.with.node.binary.scope", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 508, + "text": "M-WA5: WhatsApp policy preset missing expected endpoints/binaries after rebuild", + "polarity": "fail", + "normalized_id": "m.wa5.whatsapp.policy.preset.missing.expected.endpoints.binaries.after.rebuild", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 513, + "text": "M-WA6: Sandbox '$SANDBOX_NAME' is Ready after WhatsApp rebuild", + "polarity": "pass", + "normalized_id": "m.wa6.sandbox.sandbox.name.is.ready.after.whatsapp.rebuild", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 515, + "text": "M-WA6: Sandbox '$SANDBOX_NAME' not Ready after WhatsApp rebuild (list: ${sandbox_list:0:200})", + "polarity": "fail", + "normalized_id": "m.wa6.sandbox.sandbox.name.not.ready.after.whatsapp.rebuild.list.sandbox.list.0.200", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 521, "text": "M1: Provider '${SANDBOX_NAME}-telegram-bridge' exists in gateway", "polarity": "pass", "normalized_id": "m1.provider.sandbox.name.telegram.bridge.exists.in.gateway", @@ -7540,7 +7652,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 411, + "line": 523, "text": "M1: Provider '${SANDBOX_NAME}-telegram-bridge' not found in gateway", "polarity": "fail", "normalized_id": "m1.provider.sandbox.name.telegram.bridge.not.found.in.gateway", @@ -7548,7 +7660,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 416, + "line": 528, "text": "M2: Provider '${SANDBOX_NAME}-discord-bridge' exists in gateway", "polarity": "pass", "normalized_id": "m2.provider.sandbox.name.discord.bridge.exists.in.gateway", @@ -7556,7 +7668,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 418, + "line": 530, "text": "M2: Provider '${SANDBOX_NAME}-discord-bridge' not found in gateway", "polarity": "fail", "normalized_id": "m2.provider.sandbox.name.discord.bridge.not.found.in.gateway", @@ -7564,7 +7676,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 425, + "line": 537, "text": "M-W1: Provider '${SANDBOX_NAME}-wechat-bridge' exists in gateway", "polarity": "pass", "normalized_id": "m.w1.provider.sandbox.name.wechat.bridge.exists.in.gateway", @@ -7572,7 +7684,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 427, + "line": 539, "text": "M-W1: Provider '${SANDBOX_NAME}-wechat-bridge' not found in gateway (non-interactive QR-skip path may be broken)", "polarity": "fail", "normalized_id": "m.w1.provider.sandbox.name.wechat.bridge.not.found.in.gateway.non.interactive.qr.skip.path.may.be.broken", @@ -7580,7 +7692,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 441, + "line": 553, "text": "M3: Real Telegram token leaked into sandbox env", "polarity": "fail", "normalized_id": "m3.real.telegram.token.leaked.into.sandbox.env", @@ -7588,7 +7700,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 443, + "line": 555, "text": "M3: Sandbox TELEGRAM_BOT_TOKEN is a placeholder (not the real token)", "polarity": "pass", "normalized_id": "m3.sandbox.telegram.bot.token.is.a.placeholder.not.the.real.token", @@ -7596,7 +7708,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 454, + "line": 566, "text": "M4: Real Discord token leaked into sandbox env", "polarity": "fail", "normalized_id": "m4.real.discord.token.leaked.into.sandbox.env", @@ -7604,7 +7716,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 456, + "line": 568, "text": "M4: Sandbox DISCORD_BOT_TOKEN is a placeholder (not the real token)", "polarity": "pass", "normalized_id": "m4.sandbox.discord.bot.token.is.a.placeholder.not.the.real.token", @@ -7612,7 +7724,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 463, + "line": 575, "text": "M5: At least one messaging placeholder detected in sandbox", "polarity": "pass", "normalized_id": "m5.at.least.one.messaging.placeholder.detected.in.sandbox", @@ -7620,7 +7732,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 488, + "line": 600, "text": "M5a: Real Telegram token found in full sandbox environment dump", "polarity": "fail", "normalized_id": "m5a.real.telegram.token.found.in.full.sandbox.environment.dump", @@ -7628,7 +7740,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 490, + "line": 602, "text": "M5a: Real Telegram token absent from full sandbox environment", "polarity": "pass", "normalized_id": "m5a.real.telegram.token.absent.from.full.sandbox.environment", @@ -7636,7 +7748,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 497, + "line": 609, "text": "M5b: Real Telegram token found in sandbox process list", "polarity": "fail", "normalized_id": "m5b.real.telegram.token.found.in.sandbox.process.list", @@ -7644,7 +7756,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 499, + "line": 611, "text": "M5b: Real Telegram token absent from sandbox process list", "polarity": "pass", "normalized_id": "m5b.real.telegram.token.absent.from.sandbox.process.list", @@ -7652,7 +7764,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 506, + "line": 618, "text": "M5c: Real Telegram token found on sandbox filesystem: ${sandbox_fs_tg}", "polarity": "fail", "normalized_id": "m5c.real.telegram.token.found.on.sandbox.filesystem.sandbox.fs.tg", @@ -7660,7 +7772,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 508, + "line": 620, "text": "M5c: Real Telegram token absent from sandbox filesystem", "polarity": "pass", "normalized_id": "m5c.real.telegram.token.absent.from.sandbox.filesystem", @@ -7668,7 +7780,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 514, + "line": 626, "text": "M5d: Telegram placeholder confirmed present in sandbox environment", "polarity": "pass", "normalized_id": "m5d.telegram.placeholder.confirmed.present.in.sandbox.environment", @@ -7676,7 +7788,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 516, + "line": 628, "text": "M5d: Telegram placeholder not found in sandbox environment", "polarity": "fail", "normalized_id": "m5d.telegram.placeholder.not.found.in.sandbox.environment", @@ -7684,7 +7796,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 526, + "line": 638, "text": "M5e: Real Discord token found in full sandbox environment dump", "polarity": "fail", "normalized_id": "m5e.real.discord.token.found.in.full.sandbox.environment.dump", @@ -7692,7 +7804,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 528, + "line": 640, "text": "M5e: Real Discord token absent from full sandbox environment", "polarity": "pass", "normalized_id": "m5e.real.discord.token.absent.from.full.sandbox.environment", @@ -7700,7 +7812,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 535, + "line": 647, "text": "M5f: Real Discord token found in sandbox process list", "polarity": "fail", "normalized_id": "m5f.real.discord.token.found.in.sandbox.process.list", @@ -7708,7 +7820,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 537, + "line": 649, "text": "M5f: Real Discord token absent from sandbox process list", "polarity": "pass", "normalized_id": "m5f.real.discord.token.absent.from.sandbox.process.list", @@ -7716,7 +7828,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 543, + "line": 655, "text": "M5g: Real Discord token found on sandbox filesystem: ${sandbox_fs_dc}", "polarity": "fail", "normalized_id": "m5g.real.discord.token.found.on.sandbox.filesystem.sandbox.fs.dc", @@ -7724,7 +7836,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 545, + "line": 657, "text": "M5g: Real Discord token absent from sandbox filesystem", "polarity": "pass", "normalized_id": "m5g.real.discord.token.absent.from.sandbox.filesystem", @@ -7732,7 +7844,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 551, + "line": 663, "text": "M5h: Discord placeholder confirmed present in sandbox environment", "polarity": "pass", "normalized_id": "m5h.discord.placeholder.confirmed.present.in.sandbox.environment", @@ -7740,7 +7852,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 553, + "line": 665, "text": "M5h: Discord placeholder not found in sandbox environment", "polarity": "fail", "normalized_id": "m5h.discord.placeholder.not.found.in.sandbox.environment", @@ -7748,7 +7860,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 568, + "line": 680, "text": "M-S5a: Real Slack bot token found in full sandbox environment dump", "polarity": "fail", "normalized_id": "m.s5a.real.slack.bot.token.found.in.full.sandbox.environment.dump", @@ -7756,7 +7868,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 570, + "line": 682, "text": "M-S5a: Real Slack bot token absent from full sandbox environment", "polarity": "pass", "normalized_id": "m.s5a.real.slack.bot.token.absent.from.full.sandbox.environment", @@ -7764,7 +7876,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 577, + "line": 689, "text": "M-S5b: Real Slack bot token found in sandbox process list", "polarity": "fail", "normalized_id": "m.s5b.real.slack.bot.token.found.in.sandbox.process.list", @@ -7772,7 +7884,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 579, + "line": 691, "text": "M-S5b: Real Slack bot token absent from sandbox process list", "polarity": "pass", "normalized_id": "m.s5b.real.slack.bot.token.absent.from.sandbox.process.list", @@ -7780,7 +7892,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 585, + "line": 697, "text": "M-S5c: Real Slack bot token found on sandbox filesystem: ${sandbox_fs_sl}", "polarity": "fail", "normalized_id": "m.s5c.real.slack.bot.token.found.on.sandbox.filesystem.sandbox.fs.sl", @@ -7788,7 +7900,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 587, + "line": 699, "text": "M-S5c: Real Slack bot token absent from sandbox filesystem", "polarity": "pass", "normalized_id": "m.s5c.real.slack.bot.token.absent.from.sandbox.filesystem", @@ -7796,7 +7908,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 595, + "line": 707, "text": "M-S5d: Real Slack app token found in full sandbox environment dump", "polarity": "fail", "normalized_id": "m.s5d.real.slack.app.token.found.in.full.sandbox.environment.dump", @@ -7804,7 +7916,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 597, + "line": 709, "text": "M-S5d: Real Slack app token absent from sandbox environment", "polarity": "pass", "normalized_id": "m.s5d.real.slack.app.token.absent.from.sandbox.environment", @@ -7812,7 +7924,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 602, + "line": 714, "text": "M-S5d2: Real Slack app token found in sandbox process list", "polarity": "fail", "normalized_id": "m.s5d2.real.slack.app.token.found.in.sandbox.process.list", @@ -7820,7 +7932,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 604, + "line": 716, "text": "M-S5d2: Real Slack app token absent from sandbox process list", "polarity": "pass", "normalized_id": "m.s5d2.real.slack.app.token.absent.from.sandbox.process.list", @@ -7828,7 +7940,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 608, + "line": 720, "text": "M-S5e: Real Slack app token found on sandbox filesystem: ${sandbox_fs_sapp}", "polarity": "fail", "normalized_id": "m.s5e.real.slack.app.token.found.on.sandbox.filesystem.sandbox.fs.sapp", @@ -7836,7 +7948,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 610, + "line": 722, "text": "M-S5e: Real Slack app token absent from sandbox filesystem", "polarity": "pass", "normalized_id": "m.s5e.real.slack.app.token.absent.from.sandbox.filesystem", @@ -7844,7 +7956,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 621, + "line": 733, "text": "M-S5f: Real Slack bot/app token spliced into openclaw.json — apply_slack_token_override regression?", "polarity": "fail", "normalized_id": "m.s5f.real.slack.bot.app.token.spliced.into.openclaw.json.apply.slack.token.override.regression", @@ -7852,7 +7964,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 625, + "line": 737, "text": "M-S5f: openclaw.json holds both Bolt-shape Slack placeholders (no real token on disk)", "polarity": "pass", "normalized_id": "m.s5f.openclaw.json.holds.both.bolt.shape.slack.placeholders.no.real.token.on.disk", @@ -7860,7 +7972,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 634, + "line": 746, "text": "M-S5g: removed Slack token rewriter preload still present in NODE_OPTIONS", "polarity": "fail", "normalized_id": "m.s5g.removed.slack.token.rewriter.preload.still.present.in.node.options", @@ -7868,7 +7980,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 636, + "line": 748, "text": "M-S5g: Slack token rewriter preload absent from NODE_OPTIONS", "polarity": "pass", "normalized_id": "m.s5g.slack.token.rewriter.preload.absent.from.node.options", @@ -7876,7 +7988,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 652, + "line": 764, "text": "M-W3: Real WeChat token leaked into sandbox env", "polarity": "fail", "normalized_id": "m.w3.real.wechat.token.leaked.into.sandbox.env", @@ -7884,7 +7996,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 654, + "line": 766, "text": "M-W3: Sandbox WECHAT_BOT_TOKEN is a placeholder (not the real token)", "polarity": "pass", "normalized_id": "m.w3.sandbox.wechat.bot.token.is.a.placeholder.not.the.real.token", @@ -7892,7 +8004,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 663, + "line": 775, "text": "M-W3a: Real WeChat token found in full sandbox environment dump", "polarity": "fail", "normalized_id": "m.w3a.real.wechat.token.found.in.full.sandbox.environment.dump", @@ -7900,7 +8012,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 665, + "line": 777, "text": "M-W3a: Real WeChat token absent from full sandbox environment", "polarity": "pass", "normalized_id": "m.w3a.real.wechat.token.absent.from.full.sandbox.environment", @@ -7908,7 +8020,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 672, + "line": 784, "text": "M-W3b: Real WeChat token found in sandbox process list", "polarity": "fail", "normalized_id": "m.w3b.real.wechat.token.found.in.sandbox.process.list", @@ -7916,7 +8028,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 674, + "line": 786, "text": "M-W3b: Real WeChat token absent from sandbox process list", "polarity": "pass", "normalized_id": "m.w3b.real.wechat.token.absent.from.sandbox.process.list", @@ -7924,7 +8036,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 682, + "line": 794, "text": "M-W3c: Real WeChat token found on sandbox filesystem: ${sandbox_fs_wc}", "polarity": "fail", "normalized_id": "m.w3c.real.wechat.token.found.on.sandbox.filesystem.sandbox.fs.wc", @@ -7932,7 +8044,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 684, + "line": 796, "text": "M-W3c: Real WeChat token absent from sandbox filesystem", "polarity": "pass", "normalized_id": "m.w3c.real.wechat.token.absent.from.sandbox.filesystem", @@ -7940,7 +8052,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 690, + "line": 802, "text": "M-W3d: WeChat placeholder confirmed present in sandbox environment", "polarity": "pass", "normalized_id": "m.w3d.wechat.placeholder.confirmed.present.in.sandbox.environment", @@ -7948,7 +8060,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 692, + "line": 804, "text": "M-W3d: WeChat placeholder not found in sandbox environment", "polarity": "fail", "normalized_id": "m.w3d.wechat.placeholder.not.found.in.sandbox.environment", @@ -7956,7 +8068,55 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 715, + "line": 819, + "text": "M-WA7a: WhatsApp credential-like env var found in sandbox environment", + "polarity": "fail", + "normalized_id": "m.wa7a.whatsapp.credential.like.env.var.found.in.sandbox.environment", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 821, + "text": "M-WA7a: No WhatsApp credential-like env var present in sandbox environment", + "polarity": "pass", + "normalized_id": "m.wa7a.no.whatsapp.credential.like.env.var.present.in.sandbox.environment", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 827, + "text": "M-WA7b: WhatsApp credential placeholder found in sandbox process list", + "polarity": "fail", + "normalized_id": "m.wa7b.whatsapp.credential.placeholder.found.in.sandbox.process.list", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 829, + "text": "M-WA7b: No WhatsApp credential placeholder present in sandbox process list", + "polarity": "pass", + "normalized_id": "m.wa7b.no.whatsapp.credential.placeholder.present.in.sandbox.process.list", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 842, + "text": "M-WA7c: WhatsApp host credential material found on sandbox filesystem: ${sandbox_fs_wa}", + "polarity": "fail", + "normalized_id": "m.wa7c.whatsapp.host.credential.material.found.on.sandbox.filesystem.sandbox.fs.wa", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 844, + "text": "M-WA7c: No WhatsApp host credential material found on sandbox filesystem", + "polarity": "pass", + "normalized_id": "m.wa7c.no.whatsapp.host.credential.material.found.on.sandbox.filesystem", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 864, "text": "M6: Could not read openclaw.json channels (${channel_json:0:200})", "polarity": "fail", "normalized_id": "m6.could.not.read.openclaw.json.channels.channel.json.0.200", @@ -7964,7 +8124,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 732, + "line": 881, "text": "M6: Telegram channel botToken present in openclaw.json", "polarity": "pass", "normalized_id": "m6.telegram.channel.bottoken.present.in.openclaw.json", @@ -7972,7 +8132,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 739, + "line": 888, "text": "M7: Telegram botToken is not the host-side token (placeholder confirmed)", "polarity": "pass", "normalized_id": "m7.telegram.bottoken.is.not.the.host.side.token.placeholder.confirmed", @@ -7980,7 +8140,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 741, + "line": 890, "text": "M7: Telegram botToken matches host-side token — credential leaked into config!", "polarity": "fail", "normalized_id": "m7.telegram.bottoken.matches.host.side.token.credential.leaked.into.config", @@ -7988,7 +8148,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 756, + "line": 905, "text": "M8: Discord channel token present in openclaw.json", "polarity": "pass", "normalized_id": "m8.discord.channel.token.present.in.openclaw.json", @@ -7996,7 +8156,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 763, + "line": 912, "text": "M9: Discord token is not the host-side token (placeholder confirmed)", "polarity": "pass", "normalized_id": "m9.discord.token.is.not.the.host.side.token.placeholder.confirmed", @@ -8004,7 +8164,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 765, + "line": 914, "text": "M9: Discord token matches host-side token — credential leaked into config!", "polarity": "fail", "normalized_id": "m9.discord.token.matches.host.side.token.credential.leaked.into.config", @@ -8012,7 +8172,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 780, + "line": 929, "text": "M10: Telegram channel is enabled", "polarity": "pass", "normalized_id": "m10.telegram.channel.is.enabled", @@ -8020,7 +8180,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 795, + "line": 944, "text": "M11: Discord channel is enabled", "polarity": "pass", "normalized_id": "m11.discord.channel.is.enabled", @@ -8028,7 +8188,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 810, + "line": 959, "text": "M11b: Telegram dmPolicy is 'allowlist'", "polarity": "pass", "normalized_id": "m11b.telegram.dmpolicy.is.allowlist", @@ -8036,7 +8196,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 812, + "line": 961, "text": "M11b: Telegram dmPolicy is '$tg_dm_policy' (expected 'allowlist')", "polarity": "fail", "normalized_id": "m11b.telegram.dmpolicy.is.tg.dm.policy.expected.allowlist", @@ -8044,7 +8204,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 840, + "line": 989, "text": "M11c: Telegram allowFrom contains all expected user IDs: $tg_allow_from", "polarity": "pass", "normalized_id": "m11c.telegram.allowfrom.contains.all.expected.user.ids.tg.allow.from", @@ -8052,7 +8212,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 842, + "line": 991, "text": "M11c: Telegram allowFrom ($tg_allow_from) is missing IDs: ${missing_ids[*]} (expected all of: $TELEGRAM_IDS)", "polarity": "fail", "normalized_id": "m11c.telegram.allowfrom.tg.allow.from.is.missing.ids.missing.ids.expected.all.of.telegram.ids", @@ -8060,7 +8220,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 858, + "line": 1007, "text": "M11d: Telegram groupPolicy is 'open'", "polarity": "pass", "normalized_id": "m11d.telegram.grouppolicy.is.open", @@ -8068,7 +8228,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 860, + "line": 1009, "text": "M11d: Telegram groupPolicy is '$tg_group_policy' (expected 'open')", "polarity": "fail", "normalized_id": "m11d.telegram.grouppolicy.is.tg.group.policy.expected.open", @@ -8076,7 +8236,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 876, + "line": 1025, "text": "M11e: Slack channel configured with placeholder tokens (guard needed)", "polarity": "pass", "normalized_id": "m11e.slack.channel.configured.with.placeholder.tokens.guard.needed", @@ -8084,7 +8244,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 888, + "line": 1037, "text": "M11f: Slack dmPolicy is 'allowlist'", "polarity": "pass", "normalized_id": "m11f.slack.dmpolicy.is.allowlist", @@ -8092,7 +8252,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 890, + "line": 1039, "text": "M11f: Slack dmPolicy is '$sl_dm_policy' (expected 'allowlist')", "polarity": "fail", "normalized_id": "m11f.slack.dmpolicy.is.sl.dm.policy.expected.allowlist", @@ -8100,7 +8260,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 902, + "line": 1051, "text": "M11g: Slack groupPolicy is 'allowlist'", "polarity": "pass", "normalized_id": "m11g.slack.grouppolicy.is.allowlist", @@ -8108,7 +8268,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 904, + "line": 1053, "text": "M11g: Slack groupPolicy is '$sl_group_policy' (expected 'allowlist')", "polarity": "fail", "normalized_id": "m11g.slack.grouppolicy.is.sl.group.policy.expected.allowlist", @@ -8116,7 +8276,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 928, + "line": 1077, "text": "M11h: Slack wildcard channel config is not enabled", "polarity": "fail", "normalized_id": "m11h.slack.wildcard.channel.config.is.not.enabled", @@ -8124,7 +8284,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 930, + "line": 1079, "text": "M11h: Slack wildcard channel config does not require mention", "polarity": "fail", "normalized_id": "m11h.slack.wildcard.channel.config.does.not.require.mention", @@ -8132,7 +8292,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 932, + "line": 1081, "text": "M11h: Slack wildcard channel users is not a list", "polarity": "fail", "normalized_id": "m11h.slack.wildcard.channel.users.is.not.a.list", @@ -8140,7 +8300,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 934, + "line": 1083, "text": "M11h: Slack wildcard channel users is empty", "polarity": "fail", "normalized_id": "m11h.slack.wildcard.channel.users.is.empty", @@ -8148,7 +8308,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 949, + "line": 1098, "text": "M11h: Slack wildcard channel @mention allowlist contains expected user count (${expected_slack_id_count})", "polarity": "pass", "normalized_id": "m11h.slack.wildcard.channel.mention.allowlist.contains.expected.user.count.expected.slack.id.count", @@ -8156,7 +8316,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 951, + "line": 1100, "text": "M11h: Slack wildcard channel users missing ${#missing_slack_ids[@]} expected ID(s)", "polarity": "fail", "normalized_id": "m11h.slack.wildcard.channel.users.missing.missing.slack.ids.expected.id.s", @@ -8164,7 +8324,55 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 980, + "line": 1143, + "text": "M-WA8: WhatsApp account is enabled in openclaw.json", + "polarity": "pass", + "normalized_id": "m.wa8.whatsapp.account.is.enabled.in.openclaw.json", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 1145, + "text": "M-WA8: WhatsApp account missing or disabled in openclaw.json (${whatsapp_account_json:0:200})", + "polarity": "fail", + "normalized_id": "m.wa8.whatsapp.account.missing.or.disabled.in.openclaw.json.whatsapp.account.json.0.200", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 1149, + "text": "M-WA8a: WhatsApp health monitor is disabled for unpaired QR session", + "polarity": "pass", + "normalized_id": "m.wa8a.whatsapp.health.monitor.is.disabled.for.unpaired.qr.session", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 1151, + "text": "M-WA8a: WhatsApp health monitor is not disabled (${whatsapp_account_json:0:200})", + "polarity": "fail", + "normalized_id": "m.wa8a.whatsapp.health.monitor.is.not.disabled.whatsapp.account.json.0.200", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 1178, + "text": "M-WA9: WhatsApp config has no token/auth/session provider placeholders", + "polarity": "pass", + "normalized_id": "m.wa9.whatsapp.config.has.no.token.auth.session.provider.placeholders", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 1180, + "text": "M-WA9: WhatsApp config contains secret-like fields: ${whatsapp_secret_fields}", + "polarity": "fail", + "normalized_id": "m.wa9.whatsapp.config.contains.secret.like.fields.whatsapp.secret.fields", + "mapping_status": "unmapped" + }, + { + "script": "test/e2e/test-messaging-providers.sh", + "line": 1196, "text": "M-W8: WeChat account '$WECHAT_ACCOUNT' is enabled in openclaw.json (channels.openclaw-weixin)", "polarity": "pass", "normalized_id": "m.w8.wechat.account.wechat.account.is.enabled.in.openclaw.json.channels.openclaw.weixin", @@ -8172,7 +8380,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 996, + "line": 1212, "text": "M-W9: Real WeChat token spliced into accounts/${WECHAT_ACCOUNT}.json — seed-wechat-accounts.py placeholder regression", "polarity": "fail", "normalized_id": "m.w9.real.wechat.token.spliced.into.accounts.wechat.account.json.seed.wechat.accounts.py.placeholder.regression", @@ -8180,7 +8388,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 998, + "line": 1214, "text": "M-W9: WeChat per-account credential file uses the L7-resolved placeholder", "polarity": "pass", "normalized_id": "m.w9.wechat.per.account.credential.file.uses.the.l7.resolved.placeholder", @@ -8188,7 +8396,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1000, + "line": 1216, "text": "M-W9: WeChat per-account credential file has unexpected token shape: $(echo ", "polarity": "fail", "normalized_id": "m.w9.wechat.per.account.credential.file.has.unexpected.token.shape.echo", @@ -8196,7 +8404,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1019, + "line": 1235, "text": "M-W10: WeChat accounts.json index contains '$WECHAT_ACCOUNT'", "polarity": "pass", "normalized_id": "m.w10.wechat.accounts.json.index.contains.wechat.account", @@ -8204,7 +8412,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1021, + "line": 1237, "text": "M-W10: WeChat accounts.json missing '$WECHAT_ACCOUNT' (raw: $(echo ", "polarity": "fail", "normalized_id": "m.w10.wechat.accounts.json.missing.wechat.account.raw.echo", @@ -8212,7 +8420,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1042, + "line": 1258, "text": "M12: Node.js reached api.telegram.org (${tg_reach})", "polarity": "pass", "normalized_id": "m12.node.js.reached.api.telegram.org.tg.reach", @@ -8220,7 +8428,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1048, + "line": 1264, "text": "M12: Node.js could not reach api.telegram.org (${tg_reach:0:200})", "polarity": "fail", "normalized_id": "m12.node.js.could.not.reach.api.telegram.org.tg.reach.0.200", @@ -8228,7 +8436,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1056, + "line": 1272, "text": "M13-policy: Live policy contains Discord endpoints and Node binaries", "polarity": "pass", "normalized_id": "m13.policy.live.policy.contains.discord.endpoints.and.node.binaries", @@ -8236,7 +8444,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1058, + "line": 1274, "text": "M13-policy: Live policy is missing expected Discord preset endpoint/binary entries", "polarity": "fail", "normalized_id": "m13.policy.live.policy.is.missing.expected.discord.preset.endpoint.binary.entries", @@ -8244,7 +8452,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1064, + "line": 1280, "text": "M13-proxy: Sandbox uses the OpenShell gateway proxy", "polarity": "pass", "normalized_id": "m13.proxy.sandbox.uses.the.openshell.gateway.proxy", @@ -8252,7 +8460,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1066, + "line": 1282, "text": "M13-proxy: Sandbox proxy env does not point at OpenShell gateway: ${live_proxy_env:0:200}", "polarity": "fail", "normalized_id": "m13.proxy.sandbox.proxy.env.does.not.point.at.openshell.gateway.live.proxy.env.0.200", @@ -8260,7 +8468,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1087, + "line": 1303, "text": "M13-curl: curl unexpectedly established a tunnel to Discord; binary whitelist may be too broad", "polarity": "fail", "normalized_id": "m13.curl.curl.unexpectedly.established.a.tunnel.to.discord.binary.whitelist.may.be.too.broad", @@ -8268,7 +8476,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1130, + "line": 1346, "text": "M13: Node.js reached Discord API and CDN through the same proxy (${dc_reach//$'\\n'/ })", "polarity": "pass", "normalized_id": "m13.node.js.reached.discord.api.and.cdn.through.the.same.proxy.dc.reach.n", @@ -8276,7 +8484,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1132, + "line": 1348, "text": "M13: Node.js was denied by the proxy despite the Discord preset being applied: ${dc_reach:0:300}", "polarity": "fail", "normalized_id": "m13.node.js.was.denied.by.the.proxy.despite.the.discord.preset.being.applied.dc.reach.0.300", @@ -8284,7 +8492,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1136, + "line": 1352, "text": "M13: Node.js could not reach Discord API/CDN (${dc_reach:0:200})", "polarity": "fail", "normalized_id": "m13.node.js.could.not.reach.discord.api.cdn.dc.reach.0.200", @@ -8292,7 +8500,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1143, + "line": 1359, "text": "M13-rest-a: Hermetic fake Discord REST API started on host port ${FAKE_DISCORD_REST_PORT}", "polarity": "pass", "normalized_id": "m13.rest.a.hermetic.fake.discord.rest.api.started.on.host.port.fake.discord.rest.port", @@ -8300,7 +8508,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1152, + "line": 1368, "text": "M13-rest-b: Applied Node-only HTTPS policy for fake Discord REST API", "polarity": "pass", "normalized_id": "m13.rest.b.applied.node.only.https.policy.for.fake.discord.rest.api", @@ -8308,7 +8516,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1154, + "line": 1370, "text": "M13-rest-b: Failed to apply fake Discord REST policy: $(tail -20 /tmp/nemoclaw-fake-discord-rest-policy.log 2>/dev/null | tr '\\n' ' ' | cut -c1-300)", "polarity": "fail", "normalized_id": "m13.rest.b.failed.to.apply.fake.discord.rest.policy.tail.20.tmp.nemoclaw.fake.discord.rest.policy.log.2.dev.null.tr.n.cut.c1.300", @@ -8316,7 +8524,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1168, + "line": 1384, "text": "M13-rest-c: Node reached the fake Discord REST API through OpenShell", "polarity": "pass", "normalized_id": "m13.rest.c.node.reached.the.fake.discord.rest.api.through.openshell", @@ -8324,7 +8532,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1170, + "line": 1386, "text": "M13-rest-c: Node failed to reach fake Discord REST API: ${fake_rest_node:0:300}", "polarity": "fail", "normalized_id": "m13.rest.c.node.failed.to.reach.fake.discord.rest.api.fake.rest.node.0.300", @@ -8332,7 +8540,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1182, + "line": 1398, "text": "M13-rest-d: curl was denied before reaching the fake Discord REST API", "polarity": "pass", "normalized_id": "m13.rest.d.curl.was.denied.before.reaching.the.fake.discord.rest.api", @@ -8340,7 +8548,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1184, + "line": 1400, "text": "M13-rest-d: curl unexpectedly established a tunnel to the fake Discord REST API", "polarity": "fail", "normalized_id": "m13.rest.d.curl.unexpectedly.established.a.tunnel.to.the.fake.discord.rest.api", @@ -8348,7 +8556,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1186, + "line": 1402, "text": "M13-rest-d: Fake Discord REST curl denial had unexpected shape: ${fake_rest_curl:0:300}", "polarity": "fail", "normalized_id": "m13.rest.d.fake.discord.rest.curl.denial.had.unexpected.shape.fake.rest.curl.0.300", @@ -8356,7 +8564,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1198, + "line": 1414, "text": "M13-rest-e: Fake server saw Node but no curl request", "polarity": "pass", "normalized_id": "m13.rest.e.fake.server.saw.node.but.no.curl.request", @@ -8364,7 +8572,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1200, + "line": 1416, "text": "M13-rest-e: Unexpected fake Discord REST capture counts: ${fake_rest_capture}", "polarity": "fail", "normalized_id": "m13.rest.e.unexpected.fake.discord.rest.capture.counts.fake.rest.capture", @@ -8372,7 +8580,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1207, + "line": 1423, "text": "M13b: Hermetic fake Discord Gateway started on host port ${FAKE_DISCORD_GATEWAY_PORT}", "polarity": "pass", "normalized_id": "m13b.hermetic.fake.discord.gateway.started.on.host.port.fake.discord.gateway.port", @@ -8380,7 +8588,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1209, + "line": 1425, "text": "M13b: Failed to start hermetic fake Discord Gateway", "polarity": "fail", "normalized_id": "m13b.failed.to.start.hermetic.fake.discord.gateway", @@ -8388,7 +8596,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1214, + "line": 1430, "text": "M13c: Applied native WebSocket policy with credential rewrite for fake Discord Gateway", "polarity": "pass", "normalized_id": "m13c.applied.native.websocket.policy.with.credential.rewrite.for.fake.discord.gateway", @@ -8396,7 +8604,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1216, + "line": 1432, "text": "M13c: Failed to apply fake Discord Gateway policy: $(tail -20 /tmp/nemoclaw-fake-discord-policy.log 2>/dev/null | tr '\\n' ' ' | cut -c1-300)", "polarity": "fail", "normalized_id": "m13c.failed.to.apply.fake.discord.gateway.policy.tail.20.tmp.nemoclaw.fake.discord.policy.log.2.dev.null.tr.n.cut.c1.300", @@ -8404,7 +8612,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1226, + "line": 1442, "text": "M13d: Native WebSocket upgrade reached fake Discord Gateway through OpenShell", "polarity": "pass", "normalized_id": "m13d.native.websocket.upgrade.reached.fake.discord.gateway.through.openshell", @@ -8412,7 +8620,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1228, + "line": 1444, "text": "M13d: Native WebSocket upgrade failed: ${dc_ws_native:0:300}", "polarity": "fail", "normalized_id": "m13d.native.websocket.upgrade.failed.dc.ws.native.0.300", @@ -8420,7 +8628,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1235, + "line": 1451, "text": "M13e: Discord HELLO, placeholder IDENTIFY, READY, and heartbeat ACK completed", "polarity": "pass", "normalized_id": "m13e.discord.hello.placeholder.identify.ready.and.heartbeat.ack.completed", @@ -8428,7 +8636,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1237, + "line": 1453, "text": "M13e: Discord Gateway protocol proof incomplete: ${dc_ws_native:0:400}", "polarity": "fail", "normalized_id": "m13e.discord.gateway.protocol.proof.incomplete.dc.ws.native.0.400", @@ -8436,7 +8644,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1243, + "line": 1459, "text": "M13f: Fake Gateway received host-side Discord token; sandbox-visible IDENTIFY used only the placeholder", "polarity": "pass", "normalized_id": "m13f.fake.gateway.received.host.side.discord.token.sandbox.visible.identify.used.only.the.placeholder", @@ -8444,7 +8652,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1248, + "line": 1464, "text": "M13f: Fake Gateway did not prove placeholder-to-token rewrite at the relay boundary", "polarity": "fail", "normalized_id": "m13f.fake.gateway.did.not.prove.placeholder.to.token.rewrite.at.the.relay.boundary", @@ -8452,7 +8660,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1264, + "line": 1480, "text": "M13g: Unregistered Discord WebSocket placeholder is rejected before upstream token exposure", "polarity": "pass", "normalized_id": "m13g.unregistered.discord.websocket.placeholder.is.rejected.before.upstream.token.exposure", @@ -8460,7 +8668,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1266, + "line": 1482, "text": "M13g: Unregistered Discord WebSocket placeholder reached READY or leaked upstream", "polarity": "fail", "normalized_id": "m13g.unregistered.discord.websocket.placeholder.reached.ready.or.leaked.upstream", @@ -8468,7 +8676,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1272, + "line": 1488, "text": "M14: curl to api.telegram.org blocked (binary restriction enforced)", "polarity": "pass", "normalized_id": "m14.curl.to.api.telegram.org.blocked.binary.restriction.enforced", @@ -8476,7 +8684,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1274, + "line": 1490, "text": "M14: curl returned empty (likely blocked by policy)", "polarity": "pass", "normalized_id": "m14.curl.returned.empty.likely.blocked.by.policy", @@ -8484,7 +8692,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1278, + "line": 1494, "text": "M14: curl not available in sandbox (defense in depth)", "polarity": "pass", "normalized_id": "m14.curl.not.available.in.sandbox.defense.in.depth", @@ -8492,7 +8700,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1312, + "line": 1528, "text": "M15: Telegram getMe returned 200 — real token verified!", "polarity": "pass", "normalized_id": "m15.telegram.getme.returned.200.real.token.verified", @@ -8500,7 +8708,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1317, + "line": 1533, "text": "M15: Telegram getMe returned $tg_status — L7 proxy rewrote placeholder (fake token rejected by API)", "polarity": "pass", "normalized_id": "m15.telegram.getme.returned.tg.status.l7.proxy.rewrote.placeholder.fake.token.rejected.by.api", @@ -8508,7 +8716,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1318, + "line": 1534, "text": "M16: Full chain verified: sandbox → proxy → token rewrite → Telegram API", "polarity": "pass", "normalized_id": "m16.full.chain.verified.sandbox.proxy.token.rewrite.telegram.api", @@ -8516,7 +8724,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1324, + "line": 1540, "text": "M15: Telegram API call failed with error: ${tg_api:0:200}", "polarity": "fail", "normalized_id": "m15.telegram.api.call.failed.with.error.tg.api.0.200", @@ -8524,7 +8732,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1326, + "line": 1542, "text": "M15: Unexpected Telegram response (status=$tg_status): ${tg_api:0:200}", "polarity": "fail", "normalized_id": "m15.unexpected.telegram.response.status.tg.status.tg.api.0.200", @@ -8532,7 +8740,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1353, + "line": 1569, "text": "M17: Discord users/@me returned 200 — real token verified!", "polarity": "pass", "normalized_id": "m17.discord.users.me.returned.200.real.token.verified", @@ -8540,7 +8748,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1355, + "line": 1571, "text": "M17: Discord users/@me returned 401 — L7 proxy rewrote placeholder (fake token rejected by API)", "polarity": "pass", "normalized_id": "m17.discord.users.me.returned.401.l7.proxy.rewrote.placeholder.fake.token.rejected.by.api", @@ -8548,7 +8756,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1359, + "line": 1575, "text": "M17: Discord API call failed with error: ${dc_api:0:200}", "polarity": "fail", "normalized_id": "m17.discord.api.call.failed.with.error.dc.api.0.200", @@ -8556,7 +8764,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1361, + "line": 1577, "text": "M17: Unexpected Discord response (status=$dc_status): ${dc_api:0:200}", "polarity": "fail", "normalized_id": "m17.unexpected.discord.response.status.dc.status.dc.api.0.200", @@ -8564,7 +8772,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1373, + "line": 1589, "text": "M-S14a: Hermetic fake Slack API started on host port ${FAKE_SLACK_API_PORT}", "polarity": "pass", "normalized_id": "m.s14a.hermetic.fake.slack.api.started.on.host.port.fake.slack.api.port", @@ -8572,7 +8780,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1375, + "line": 1591, "text": "M-S14a: Failed to start hermetic fake Slack API", "polarity": "fail", "normalized_id": "m.s14a.failed.to.start.hermetic.fake.slack.api", @@ -8580,7 +8788,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1380, + "line": 1596, "text": "M-S14b: Applied REST policy for hermetic fake Slack API", "polarity": "pass", "normalized_id": "m.s14b.applied.rest.policy.for.hermetic.fake.slack.api", @@ -8588,7 +8796,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1382, + "line": 1598, "text": "M-S14b: Failed to apply fake Slack API policy: $(tail -20 /tmp/nemoclaw-fake-slack-policy.log 2>/dev/null | tr '\\n' ' ' | cut -c1-300)", "polarity": "fail", "normalized_id": "m.s14b.failed.to.apply.fake.slack.api.policy.tail.20.tmp.nemoclaw.fake.slack.policy.log.2.dev.null.tr.n.cut.c1.300", @@ -8596,7 +8804,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1464, + "line": 1680, "text": "M-S15: Slack auth.test returned ok:true — real token round-trip verified!", "polarity": "pass", "normalized_id": "m.s15.slack.auth.test.returned.ok.true.real.token.round.trip.verified", @@ -8604,7 +8812,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1466, + "line": 1682, "text": "M-S15: Slack auth.test returned invalid_auth — full chain verified (OpenShell alias rewrite → fake Slack)", "polarity": "pass", "normalized_id": "m.s15.slack.auth.test.returned.invalid.auth.full.chain.verified.openshell.alias.rewrite.fake.slack", @@ -8612,7 +8820,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1469, + "line": 1685, "text": "M-S15a: fake Slack saw host-side bot token in header and urlencoded body", "polarity": "pass", "normalized_id": "m.s15a.fake.slack.saw.host.side.bot.token.in.header.and.urlencoded.body", @@ -8620,7 +8828,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1471, + "line": 1687, "text": "M-S15a: fake Slack capture did not prove bot header/body rewrite: ${sl_capture:0:300}", "polarity": "fail", "normalized_id": "m.s15a.fake.slack.capture.did.not.prove.bot.header.body.rewrite.sl.capture.0.300", @@ -8628,7 +8836,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1476, + "line": 1692, "text": "M-S15: Slack API call failed with error: ${sl_api:0:200}", "polarity": "fail", "normalized_id": "m.s15.slack.api.call.failed.with.error.sl.api.0.200", @@ -8636,7 +8844,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1478, + "line": 1694, "text": "M-S15: OpenShell did not resolve the Bolt-shape alias", "polarity": "fail", "normalized_id": "m.s15.openshell.did.not.resolve.the.bolt.shape.alias", @@ -8644,7 +8852,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1480, + "line": 1696, "text": "M-S15: L7 proxy did not substitute the canonical placeholder — substitution chain broken", "polarity": "fail", "normalized_id": "m.s15.l7.proxy.did.not.substitute.the.canonical.placeholder.substitution.chain.broken", @@ -8652,7 +8860,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1482, + "line": 1698, "text": "M-S15: Unexpected Slack response (status=$sl_status): ${sl_api:0:200}", "polarity": "fail", "normalized_id": "m.s15.unexpected.slack.response.status.sl.status.sl.api.0.200", @@ -8660,7 +8868,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1503, + "line": 1719, "text": "M-S15b: L7 proxy substitutes openshell:resolve:env:SLACK_BOT_TOKEN at egress (parallels Telegram M15 / Discord M17)", "polarity": "pass", "normalized_id": "m.s15b.l7.proxy.substitutes.openshell.resolve.env.slack.bot.token.at.egress.parallels.telegram.m15.discord.m17", @@ -8668,7 +8876,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1507, + "line": 1723, "text": "M-S15b: L7 proxy passed canonical placeholder through unchanged — substitution not happening for SLACK_BOT_TOKEN", "polarity": "fail", "normalized_id": "m.s15b.l7.proxy.passed.canonical.placeholder.through.unchanged.substitution.not.happening.for.slack.bot.token", @@ -8676,7 +8884,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1509, + "line": 1725, "text": "M-S15b: Unexpected response (status=$sl_canon_status): ${sl_canonical:0:200}", "polarity": "fail", "normalized_id": "m.s15b.unexpected.response.status.sl.canon.status.sl.canonical.0.200", @@ -8684,7 +8892,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1530, + "line": 1746, "text": "M-S15c: unset-var failed closed before upstream exposure", "polarity": "pass", "normalized_id": "m.s15c.unset.var.failed.closed.before.upstream.exposure", @@ -8692,7 +8900,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1532, + "line": 1748, "text": "M-S15c: unset-var triggered connection-level failure — proxy refuses to forward unsubstituted placeholder", "polarity": "pass", "normalized_id": "m.s15c.unset.var.triggered.connection.level.failure.proxy.refuses.to.forward.unsubstituted.placeholder", @@ -8700,7 +8908,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1534, + "line": 1750, "text": "M-S15c: unset-var returned HTTP 200 — proxy passed canonical placeholder through unchanged for unset env (substitution may be a no-op)", "polarity": "fail", "normalized_id": "m.s15c.unset.var.returned.http.200.proxy.passed.canonical.placeholder.through.unchanged.for.unset.env.substitution.may.be.a.no.op", @@ -8708,7 +8916,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1536, + "line": 1752, "text": "M-S15c: unset-var request reached fake Slack — unresolved placeholder escaped the proxy boundary", "polarity": "fail", "normalized_id": "m.s15c.unset.var.request.reached.fake.slack.unresolved.placeholder.escaped.the.proxy.boundary", @@ -8716,7 +8924,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1557, + "line": 1773, "text": "M-S16: apps.connections.open returned ok:true — real xapp token round-trip verified!", "polarity": "pass", "normalized_id": "m.s16.apps.connections.open.returned.ok.true.real.xapp.token.round.trip.verified", @@ -8724,7 +8932,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1559, + "line": 1775, "text": "M-S16: apps.connections.open auth-rejected — Socket Mode HTTPS leg verified (OpenShell alias rewrite → fake Slack)", "polarity": "pass", "normalized_id": "m.s16.apps.connections.open.auth.rejected.socket.mode.https.leg.verified.openshell.alias.rewrite.fake.slack", @@ -8732,7 +8940,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1562, + "line": 1778, "text": "M-S16a: fake Slack saw host-side app token in header and urlencoded body", "polarity": "pass", "normalized_id": "m.s16a.fake.slack.saw.host.side.app.token.in.header.and.urlencoded.body", @@ -8740,7 +8948,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1564, + "line": 1780, "text": "M-S16a: fake Slack capture did not prove app header/body rewrite: ${sl_app_capture:0:300}", "polarity": "fail", "normalized_id": "m.s16a.fake.slack.capture.did.not.prove.app.header.body.rewrite.sl.app.capture.0.300", @@ -8748,7 +8956,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1569, + "line": 1785, "text": "M-S16: OpenShell did not resolve the xapp- alias for Socket Mode path", "polarity": "fail", "normalized_id": "m.s16.openshell.did.not.resolve.the.xapp.alias.for.socket.mode.path", @@ -8756,7 +8964,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1571, + "line": 1787, "text": "M-S16: Unexpected apps.connections.open response (status=$sl_app_status): ${sl_app_api:0:200}", "polarity": "fail", "normalized_id": "m.s16.unexpected.apps.connections.open.response.status.sl.app.status.sl.app.api.0.200", @@ -8764,7 +8972,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1595, + "line": 1811, "text": "M-S16b: unset app-token failed closed before upstream exposure", "polarity": "pass", "normalized_id": "m.s16b.unset.app.token.failed.closed.before.upstream.exposure", @@ -8772,7 +8980,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1597, + "line": 1813, "text": "M-S16b: L7 proxy substitutes openshell:resolve:env:SLACK_APP_TOKEN at egress (unset-var control diverged)", "polarity": "pass", "normalized_id": "m.s16b.l7.proxy.substitutes.openshell.resolve.env.slack.app.token.at.egress.unset.var.control.diverged", @@ -8780,7 +8988,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1599, + "line": 1815, "text": "M-S16b: unset app-token env returned HTTP 200 — proxy may be passing canonical placeholders through unchanged", "polarity": "fail", "normalized_id": "m.s16b.unset.app.token.env.returned.http.200.proxy.may.be.passing.canonical.placeholders.through.unchanged", @@ -8788,7 +8996,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1601, + "line": 1817, "text": "M-S16b: unset app-token request reached fake Slack — unresolved placeholder escaped the proxy boundary", "polarity": "fail", "normalized_id": "m.s16b.unset.app.token.request.reached.fake.slack.unresolved.placeholder.escaped.the.proxy.boundary", @@ -8796,7 +9004,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1610, + "line": 1826, "text": "M-S16b: L7 proxy passed canonical placeholder through unchanged for SLACK_APP_TOKEN", "polarity": "fail", "normalized_id": "m.s16b.l7.proxy.passed.canonical.placeholder.through.unchanged.for.slack.app.token", @@ -8804,7 +9012,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1612, + "line": 1828, "text": "M-S16b: Unexpected response (status=$sl_app_canon_status): ${sl_app_canonical:0:200}", "polarity": "fail", "normalized_id": "m.s16b.unexpected.response.status.sl.app.canon.status.sl.app.canonical.0.200", @@ -8812,7 +9020,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1631, + "line": 1847, "text": "M-S17: Slack channel @mention allowlist accepts configured user and denies another user", "polarity": "pass", "normalized_id": "m.s17.slack.channel.mention.allowlist.accepts.configured.user.and.denies.another.user", @@ -8820,7 +9028,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1634, + "line": 1850, "text": "M-S17a: fake Slack saw host-side bot token for channel reply", "polarity": "pass", "normalized_id": "m.s17a.fake.slack.saw.host.side.bot.token.for.channel.reply", @@ -8828,7 +9036,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1636, + "line": 1852, "text": "M-S17a: fake Slack capture did not prove channel reply token rewrite: ${sl_post_capture:0:300}", "polarity": "fail", "normalized_id": "m.s17a.fake.slack.capture.did.not.prove.channel.reply.token.rewrite.sl.post.capture.0.300", @@ -8836,7 +9044,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1640, + "line": 1856, "text": "M-S17b: fake Slack captured non-secret channel/text metadata for channel reply", "polarity": "pass", "normalized_id": "m.s17b.fake.slack.captured.non.secret.channel.text.metadata.for.channel.reply", @@ -8844,7 +9052,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1642, + "line": 1858, "text": "M-S17b: fake Slack did not capture expected channel reply metadata: ${sl_message_capture:0:300}", "polarity": "fail", "normalized_id": "m.s17b.fake.slack.did.not.capture.expected.channel.reply.metadata.sl.message.capture.0.300", @@ -8852,7 +9060,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1649, + "line": 1865, "text": "M-S17: Slack channel @mention proof failed: ${sl_channel_proof:0:500}", "polarity": "fail", "normalized_id": "m.s17.slack.channel.mention.proof.failed.sl.channel.proof.0.500", @@ -8860,7 +9068,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1664, + "line": 1880, "text": "M18: Telegram getMe returned 200 with real token", "polarity": "pass", "normalized_id": "m18.telegram.getme.returned.200.with.real.token", @@ -8868,7 +9076,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1666, + "line": 1882, "text": "M18b: Telegram response contains ok:true", "polarity": "pass", "normalized_id": "m18b.telegram.response.contains.ok.true", @@ -8876,7 +9084,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1669, + "line": 1885, "text": "M18: Expected Telegram getMe 200 with real token, got: $tg_status", "polarity": "fail", "normalized_id": "m18.expected.telegram.getme.200.with.real.token.got.tg.status", @@ -8884,7 +9092,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1699, + "line": 1915, "text": "M19: Telegram sendMessage succeeded", "polarity": "pass", "normalized_id": "m19.telegram.sendmessage.succeeded", @@ -8892,7 +9100,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1701, + "line": 1917, "text": "M19: Telegram sendMessage failed: ${send_result:0:200}", "polarity": "fail", "normalized_id": "m19.telegram.sendmessage.failed.send.result.0.200", @@ -8900,7 +9108,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1713, + "line": 1929, "text": "M20: Discord users/@me returned 200 with real token", "polarity": "pass", "normalized_id": "m20.discord.users.me.returned.200.with.real.token", @@ -8908,7 +9116,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1715, + "line": 1931, "text": "M20: Expected Discord users/@me 200 with real token, got: $dc_status", "polarity": "fail", "normalized_id": "m20.expected.discord.users.me.200.with.real.token.got.dc.status", @@ -8916,7 +9124,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1747, + "line": 1963, "text": "S1: Gateway is serving on port 18789 — Slack auth failure did not crash it", "polarity": "pass", "normalized_id": "s1.gateway.is.serving.on.port.18789.slack.auth.failure.did.not.crash.it", @@ -8924,7 +9132,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1749, + "line": 1965, "text": "S1: Gateway is not serving on port 18789 (${gw_port:0:200})", "polarity": "fail", "normalized_id": "s1.gateway.is.not.serving.on.port.18789.gw.port.0.200", @@ -8932,7 +9140,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1775, + "line": 1991, "text": "S2: Gateway log shows Slack rejection was caught by channel guard", "polarity": "pass", "normalized_id": "s2.gateway.log.shows.slack.rejection.was.caught.by.channel.guard", @@ -8940,7 +9148,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1800, + "line": 2016, "text": "Cleanup: Sandbox '$SANDBOX_NAME' intentionally kept", "polarity": "pass", "normalized_id": "cleanup.sandbox.sandbox.name.intentionally.kept", @@ -8948,7 +9156,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1802, + "line": 2018, "text": "Cleanup: Sandbox '$SANDBOX_NAME' still present after cleanup", "polarity": "fail", "normalized_id": "cleanup.sandbox.sandbox.name.still.present.after.cleanup", @@ -8956,7 +9164,7 @@ }, { "script": "test/e2e/test-messaging-providers.sh", - "line": 1804, + "line": 2020, "text": "Cleanup: Sandbox '$SANDBOX_NAME' removed", "polarity": "pass", "normalized_id": "cleanup.sandbox.sandbox.name.removed", @@ -10983,6 +11191,115 @@ } ] }, + { + "script": "test/e2e/test-openclaw-plugin-runtime-exdev.sh", + "assertions": [ + { + "script": "test/e2e/test-openclaw-plugin-runtime-exdev.sh", + "line": 68, + "text": "Docker is running", + "polarity": "pass", + "normalized_id": "docker.is.running", + "mapping_status": "deferred" + }, + { + "script": "test/e2e/test-openclaw-plugin-runtime-exdev.sh", + "line": 70, + "text": "Docker is not running", + "polarity": "fail", + "normalized_id": "docker.is.not.running", + "mapping_status": "deferred" + }, + { + "script": "test/e2e/test-openclaw-plugin-runtime-exdev.sh", + "line": 75, + "text": "NVIDIA_API_KEY is set", + "polarity": "pass", + "normalized_id": "nvidia.api.key.is.set", + "mapping_status": "deferred" + }, + { + "script": "test/e2e/test-openclaw-plugin-runtime-exdev.sh", + "line": 77, + "text": "NVIDIA_API_KEY is required and must start with nvapi-", + "polarity": "fail", + "normalized_id": "nvidia.api.key.is.required.and.must.start.with.nvapi", + "mapping_status": "deferred" + }, + { + "script": "test/e2e/test-openclaw-plugin-runtime-exdev.sh", + "line": 90, + "text": "nemoclaw is available: $(nemoclaw --version 2>/dev/null || echo unknown)", + "polarity": "pass", + "normalized_id": "nemoclaw.is.available.nemoclaw.version.2.dev.null.echo.unknown", + "mapping_status": "deferred" + }, + { + "script": "test/e2e/test-openclaw-plugin-runtime-exdev.sh", + "line": 92, + "text": "nemoclaw not found after install", + "polarity": "fail", + "normalized_id": "nemoclaw.not.found.after.install", + "mapping_status": "deferred" + }, + { + "script": "test/e2e/test-openclaw-plugin-runtime-exdev.sh", + "line": 135, + "text": "fresh sandbox onboard completed", + "polarity": "pass", + "normalized_id": "fresh.sandbox.onboard.completed", + "mapping_status": "deferred" + }, + { + "script": "test/e2e/test-openclaw-plugin-runtime-exdev.sh", + "line": 137, + "text": "fresh sandbox onboard failed (exit ${onboard_rc}); see ${ONBOARD_LOG}", + "polarity": "fail", + "normalized_id": "fresh.sandbox.onboard.failed.exit.onboard.rc.see.onboard.log", + "mapping_status": "deferred" + }, + { + "script": "test/e2e/test-openclaw-plugin-runtime-exdev.sh", + "line": 187, + "text": "OpenClaw-style plugin runtime deps replacement hit #3513 EXDEV failure", + "polarity": "fail", + "normalized_id": "openclaw.style.plugin.runtime.deps.replacement.hit.3513.exdev.failure", + "mapping_status": "deferred" + }, + { + "script": "test/e2e/test-openclaw-plugin-runtime-exdev.sh", + "line": 193, + "text": "runtime deps replacement exited ${agent_rc}; see ${AGENT_LOG}", + "polarity": "fail", + "normalized_id": "runtime.deps.replacement.exited.agent.rc.see.agent.log", + "mapping_status": "deferred" + }, + { + "script": "test/e2e/test-openclaw-plugin-runtime-exdev.sh", + "line": 198, + "text": "OpenClaw-style plugin runtime-deps replacement completed across filesystems", + "polarity": "pass", + "normalized_id": "openclaw.style.plugin.runtime.deps.replacement.completed.across.filesystems", + "mapping_status": "deferred" + }, + { + "script": "test/e2e/test-openclaw-plugin-runtime-exdev.sh", + "line": 200, + "text": "runtime deps replacement exited 0 but success marker was missing; see ${AGENT_LOG}", + "polarity": "fail", + "normalized_id": "runtime.deps.replacement.exited.0.but.success.marker.was.missing.see.agent.log", + "mapping_status": "deferred" + }, + { + "script": "test/e2e/test-openclaw-plugin-runtime-exdev.sh", + "line": 206, + "text": "OpenClaw plugin runtime-deps EXDEV guard passed", + "polarity": "pass", + "normalized_id": "openclaw.plugin.runtime.deps.exdev.guard.passed", + "mapping_status": "deferred" + } + ] + }, { "script": "test/e2e/test-openshell-gateway-upgrade.sh", "assertions": [ @@ -16347,8 +16664,8 @@ } ], "totals": { - "scripts": 52, - "assertions": 2010, + "scripts": 53, + "assertions": 2049, "zero_assertion_scripts": 2 } } diff --git a/test/e2e/docs/parity-map.yaml b/test/e2e/docs/parity-map.yaml index efa7932f8bd..4b222f3efab 100644 --- a/test/e2e/docs/parity-map.yaml +++ b/test/e2e/docs/parity-map.yaml @@ -6850,6 +6850,76 @@ scripts: reason: legacy assertion is obsolete or negative cleanup behavior after scenario migration reviewer: e2e-maintainers approved_at: '2026-05-13' + test-openclaw-plugin-runtime-exdev.sh: + scenario: '' + status: deferred + bucket: openclaw-plugin-runtime-exdev + assertions: + - legacy: Docker is running + status: deferred + reason: regression guard validates #3513 before migration to scenario framework + owner: e2e-maintainers + runner_requirement: sandbox runner with Docker and NemoClaw/OpenShell CLIs + - legacy: Docker is not running + status: deferred + reason: prerequisite failure for #3513 regression guard + owner: e2e-maintainers + runner_requirement: sandbox runner with Docker and NemoClaw/OpenShell CLIs + - legacy: NVIDIA_API_KEY is set + status: deferred + reason: regression guard validates #3513 with live provider credentials before scenario migration + owner: e2e-maintainers + runner_requirement: sandbox runner with NVIDIA_API_KEY + - legacy: NVIDIA_API_KEY is required and must start with nvapi- + status: deferred + reason: prerequisite failure for #3513 regression guard + owner: e2e-maintainers + runner_requirement: sandbox runner with NVIDIA_API_KEY + - legacy: 'nemoclaw is available: $(nemoclaw --version 2>/dev/null || echo unknown)' + status: deferred + reason: regression guard validates checkout install before scenario migration + owner: e2e-maintainers + runner_requirement: local CLI build with installer + - legacy: nemoclaw not found after install + status: deferred + reason: prerequisite failure for #3513 regression guard + owner: e2e-maintainers + runner_requirement: local CLI build with installer + - legacy: fresh sandbox onboard completed + status: deferred + reason: regression guard validates #3513 on a fresh sandbox before scenario migration + owner: e2e-maintainers + runner_requirement: sandbox runner with Docker and OpenShell + - legacy: fresh sandbox onboard failed (exit ${onboard_rc}); see ${ONBOARD_LOG} + status: deferred + reason: setup failure evidence for #3513 regression guard + owner: e2e-maintainers + runner_requirement: sandbox runner with Docker and OpenShell + - legacy: 'OpenClaw-style plugin runtime deps replacement hit #3513 EXDEV failure' + status: deferred + reason: executable red assertion for #3513 regression guard + owner: e2e-maintainers + runner_requirement: sandbox runner with cross-filesystem /dev/shm and /sandbox paths + - legacy: runtime deps replacement exited ${agent_rc}; see ${AGENT_LOG} + status: deferred + reason: runtime failure evidence for #3513 regression guard + owner: e2e-maintainers + runner_requirement: sandbox runner with OpenShell exec + - legacy: OpenClaw-style plugin runtime-deps replacement completed across filesystems + status: deferred + reason: expected green assertion after #3513 fix before migration to scenario framework + owner: e2e-maintainers + runner_requirement: sandbox runner with cross-filesystem /dev/shm and /sandbox paths + - legacy: runtime deps replacement exited 0 but success marker was missing; see ${AGENT_LOG} + status: deferred + reason: runtime success-marker failure evidence for #3513 regression guard + owner: e2e-maintainers + runner_requirement: sandbox runner with OpenShell exec + - legacy: OpenClaw plugin runtime-deps EXDEV guard passed + status: deferred + reason: expected green summary after #3513 fix before migration to scenario framework + owner: e2e-maintainers + runner_requirement: sandbox runner with OpenShell exec test-openshell-gateway-upgrade.sh: scenario: ubuntu-repo-cloud-openclaw status: migrated diff --git a/test/e2e/test-openclaw-plugin-runtime-exdev.sh b/test/e2e/test-openclaw-plugin-runtime-exdev.sh index 77b249a4458..55554501491 100755 --- a/test/e2e/test-openclaw-plugin-runtime-exdev.sh +++ b/test/e2e/test-openclaw-plugin-runtime-exdev.sh @@ -56,12 +56,12 @@ open(path, "w").write(text) PY } -cleanup_logs() { +redact_logs() { redact_file "$ONBOARD_LOG" redact_file "$AGENT_LOG" redact_file "$DF_LOG" } -trap cleanup_logs EXIT +trap redact_logs EXIT section "Prerequisites" if docker info >/dev/null 2>&1; then @@ -97,7 +97,6 @@ section "Fresh sandbox onboard" rm -f "$HOME/.nemoclaw/onboard.lock" 2>/dev/null || true nemoclaw "$SANDBOX_NAME" destroy --yes >/dev/null 2>&1 || true - python3 - "${REPO}" <<'PY' import sys from pathlib import Path @@ -130,7 +129,7 @@ env \ "$TIMEOUT_CMD" 1500 nemoclaw onboard --fresh --non-interactive --yes-i-accept-third-party-software --agent openclaw --from "$REPO/Dockerfile" \ >"$ONBOARD_LOG" 2>&1 onboard_rc=$? -redact_file "$ONBOARD_LOG" +redact_logs if [ "$onboard_rc" -eq 0 ]; then pass "fresh sandbox onboard completed" else @@ -141,7 +140,7 @@ fi section "Filesystem layout evidence" openshell sandbox exec --name "$SANDBOX_NAME" -- sh -lc 'df -PT / /tmp /dev/shm /sandbox /sandbox/.openclaw/plugin-runtime-deps 2>&1' \ >"$DF_LOG" 2>&1 || true -redact_file "$DF_LOG" +redact_logs info "Filesystem layout captured in ${DF_LOG}" section "Bundled plugin runtime-deps cross-device replacement" @@ -151,7 +150,8 @@ agent_rc=0 # contents into a staging dir adjacent to the source and then renameSyncs that # staged node_modules dir into the final plugin-runtime-deps target. When source # is on tmpfs (/dev/shm) and target is under /sandbox, unfixed code throws EXDEV. -remote_script_b64=$(cat <<'REMOTE' | base64 | tr -d '\n' +remote_script_b64=$( + cat <<'REMOTE' | base64 | tr -d '\n' set -eu rm -rf /sandbox/.openclaw/plugin-runtime-deps/exdev-guard 2>/dev/null || true rm -rf /dev/shm/nemoclaw-exdev-source 2>/dev/null || true @@ -181,7 +181,7 @@ REMOTE remote_cmd="printf '%s' '${remote_script_b64}' | base64 -d > /tmp/nemoclaw-exdev-guard.sh && sh /tmp/nemoclaw-exdev-guard.sh" "$TIMEOUT_CMD" 60 openshell sandbox exec --name "$SANDBOX_NAME" -- sh -lc "$remote_cmd" \ >"$AGENT_LOG" 2>&1 || agent_rc=$? -redact_file "$AGENT_LOG" +redact_logs if grep -qiE 'EXDEV: cross-device link not permitted|cross-device link not permitted' "$AGENT_LOG"; then fail "OpenClaw-style plugin runtime deps replacement hit #3513 EXDEV failure" From 8b632bb941e95b15d329c8fc5d8777f12a32a807 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Tue, 19 May 2026 10:08:38 -0400 Subject: [PATCH 12/12] fix(e2e): map whatsapp parity assertions --- test/e2e/docs/parity-inventory.generated.json | 56 ++++---- test/e2e/docs/parity-map.yaml | 130 ++++++++++++++++++ 2 files changed, 158 insertions(+), 28 deletions(-) diff --git a/test/e2e/docs/parity-inventory.generated.json b/test/e2e/docs/parity-inventory.generated.json index dce70a0efc0..12ae1d302bd 100644 --- a/test/e2e/docs/parity-inventory.generated.json +++ b/test/e2e/docs/parity-inventory.generated.json @@ -7536,7 +7536,7 @@ "text": "M-WA0: channels add whatsapp registered QR-only channel", "polarity": "pass", "normalized_id": "m.wa0.channels.add.whatsapp.registered.qr.only.channel", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -7544,7 +7544,7 @@ "text": "M-WA0: channels add whatsapp failed or did not register channel", "polarity": "fail", "normalized_id": "m.wa0.channels.add.whatsapp.failed.or.did.not.register.channel", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -7552,7 +7552,7 @@ "text": "M-WA1: Unexpected WhatsApp bridge provider exists in gateway", "polarity": "fail", "normalized_id": "m.wa1.unexpected.whatsapp.bridge.provider.exists.in.gateway", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -7560,7 +7560,7 @@ "text": "M-WA1: WhatsApp QR-only channel creates no bridge provider", "polarity": "pass", "normalized_id": "m.wa1.whatsapp.qr.only.channel.creates.no.bridge.provider", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -7568,7 +7568,7 @@ "text": "M-WA2: registry.messagingChannels contains whatsapp after channel add", "polarity": "pass", "normalized_id": "m.wa2.registry.messagingchannels.contains.whatsapp.after.channel.add", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -7576,7 +7576,7 @@ "text": "M-WA2: registry.messagingChannels missing whatsapp after channel add ($(registry_field messagingChannels))", "polarity": "fail", "normalized_id": "m.wa2.registry.messagingchannels.missing.whatsapp.after.channel.add.registry.field.messagingchannels", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -7584,7 +7584,7 @@ "text": "M-WA3: WhatsApp policy preset applied before rebuild", "polarity": "pass", "normalized_id": "m.wa3.whatsapp.policy.preset.applied.before.rebuild", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -7592,7 +7592,7 @@ "text": "M-WA3: WhatsApp policy preset missing expected endpoints before rebuild", "polarity": "fail", "normalized_id": "m.wa3.whatsapp.policy.preset.missing.expected.endpoints.before.rebuild", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -7600,7 +7600,7 @@ "text": "M-WA4: Rebuild completed after WhatsApp channel add", "polarity": "pass", "normalized_id": "m.wa4.rebuild.completed.after.whatsapp.channel.add", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -7608,7 +7608,7 @@ "text": "M-WA4: Rebuild failed after WhatsApp channel add", "polarity": "fail", "normalized_id": "m.wa4.rebuild.failed.after.whatsapp.channel.add", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -7616,7 +7616,7 @@ "text": "M-WA5: WhatsApp policy preset survived rebuild with Node binary scope", "polarity": "pass", "normalized_id": "m.wa5.whatsapp.policy.preset.survived.rebuild.with.node.binary.scope", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -7624,7 +7624,7 @@ "text": "M-WA5: WhatsApp policy preset missing expected endpoints/binaries after rebuild", "polarity": "fail", "normalized_id": "m.wa5.whatsapp.policy.preset.missing.expected.endpoints.binaries.after.rebuild", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -7632,7 +7632,7 @@ "text": "M-WA6: Sandbox '$SANDBOX_NAME' is Ready after WhatsApp rebuild", "polarity": "pass", "normalized_id": "m.wa6.sandbox.sandbox.name.is.ready.after.whatsapp.rebuild", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -7640,7 +7640,7 @@ "text": "M-WA6: Sandbox '$SANDBOX_NAME' not Ready after WhatsApp rebuild (list: ${sandbox_list:0:200})", "polarity": "fail", "normalized_id": "m.wa6.sandbox.sandbox.name.not.ready.after.whatsapp.rebuild.list.sandbox.list.0.200", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -8072,7 +8072,7 @@ "text": "M-WA7a: WhatsApp credential-like env var found in sandbox environment", "polarity": "fail", "normalized_id": "m.wa7a.whatsapp.credential.like.env.var.found.in.sandbox.environment", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -8080,7 +8080,7 @@ "text": "M-WA7a: No WhatsApp credential-like env var present in sandbox environment", "polarity": "pass", "normalized_id": "m.wa7a.no.whatsapp.credential.like.env.var.present.in.sandbox.environment", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -8088,7 +8088,7 @@ "text": "M-WA7b: WhatsApp credential placeholder found in sandbox process list", "polarity": "fail", "normalized_id": "m.wa7b.whatsapp.credential.placeholder.found.in.sandbox.process.list", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -8096,7 +8096,7 @@ "text": "M-WA7b: No WhatsApp credential placeholder present in sandbox process list", "polarity": "pass", "normalized_id": "m.wa7b.no.whatsapp.credential.placeholder.present.in.sandbox.process.list", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -8104,7 +8104,7 @@ "text": "M-WA7c: WhatsApp host credential material found on sandbox filesystem: ${sandbox_fs_wa}", "polarity": "fail", "normalized_id": "m.wa7c.whatsapp.host.credential.material.found.on.sandbox.filesystem.sandbox.fs.wa", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -8112,7 +8112,7 @@ "text": "M-WA7c: No WhatsApp host credential material found on sandbox filesystem", "polarity": "pass", "normalized_id": "m.wa7c.no.whatsapp.host.credential.material.found.on.sandbox.filesystem", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -8328,7 +8328,7 @@ "text": "M-WA8: WhatsApp account is enabled in openclaw.json", "polarity": "pass", "normalized_id": "m.wa8.whatsapp.account.is.enabled.in.openclaw.json", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -8336,7 +8336,7 @@ "text": "M-WA8: WhatsApp account missing or disabled in openclaw.json (${whatsapp_account_json:0:200})", "polarity": "fail", "normalized_id": "m.wa8.whatsapp.account.missing.or.disabled.in.openclaw.json.whatsapp.account.json.0.200", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -8344,7 +8344,7 @@ "text": "M-WA8a: WhatsApp health monitor is disabled for unpaired QR session", "polarity": "pass", "normalized_id": "m.wa8a.whatsapp.health.monitor.is.disabled.for.unpaired.qr.session", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -8352,7 +8352,7 @@ "text": "M-WA8a: WhatsApp health monitor is not disabled (${whatsapp_account_json:0:200})", "polarity": "fail", "normalized_id": "m.wa8a.whatsapp.health.monitor.is.not.disabled.whatsapp.account.json.0.200", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -8360,7 +8360,7 @@ "text": "M-WA9: WhatsApp config has no token/auth/session provider placeholders", "polarity": "pass", "normalized_id": "m.wa9.whatsapp.config.has.no.token.auth.session.provider.placeholders", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -8368,7 +8368,7 @@ "text": "M-WA9: WhatsApp config contains secret-like fields: ${whatsapp_secret_fields}", "polarity": "fail", "normalized_id": "m.wa9.whatsapp.config.contains.secret.like.fields.whatsapp.secret.fields", - "mapping_status": "unmapped" + "mapping_status": "deferred" }, { "script": "test/e2e/test-messaging-providers.sh", @@ -11244,7 +11244,7 @@ }, { "script": "test/e2e/test-openclaw-plugin-runtime-exdev.sh", - "line": 135, + "line": 134, "text": "fresh sandbox onboard completed", "polarity": "pass", "normalized_id": "fresh.sandbox.onboard.completed", @@ -11252,7 +11252,7 @@ }, { "script": "test/e2e/test-openclaw-plugin-runtime-exdev.sh", - "line": 137, + "line": 136, "text": "fresh sandbox onboard failed (exit ${onboard_rc}); see ${ONBOARD_LOG}", "polarity": "fail", "normalized_id": "fresh.sandbox.onboard.failed.exit.onboard.rc.see.onboard.log", diff --git a/test/e2e/docs/parity-map.yaml b/test/e2e/docs/parity-map.yaml index 4b222f3efab..4da0e2435a4 100644 --- a/test/e2e/docs/parity-map.yaml +++ b/test/e2e/docs/parity-map.yaml @@ -5680,6 +5680,136 @@ scripts: reason: live legacy behavior requires non-deterministic infrastructure; retained for bucket parity tracking owner: e2e-maintainers secret_requirement: WeChat test credentials + - legacy: 'M-WA0: channels add whatsapp registered QR-only channel' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA0: channels add whatsapp failed or did not register channel' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA1: Unexpected WhatsApp bridge provider exists in gateway' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA1: WhatsApp QR-only channel creates no bridge provider' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA2: registry.messagingChannels contains whatsapp after channel add' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA2: registry.messagingChannels missing whatsapp after channel add ($(registry_field messagingChannels))' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA3: WhatsApp policy preset applied before rebuild' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA3: WhatsApp policy preset missing expected endpoints before rebuild' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA4: Rebuild completed after WhatsApp channel add' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA4: Rebuild failed after WhatsApp channel add' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA5: WhatsApp policy preset survived rebuild with Node binary scope' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA5: WhatsApp policy preset missing expected endpoints/binaries after rebuild' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA6: Sandbox ''$SANDBOX_NAME'' is Ready after WhatsApp rebuild' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA6: Sandbox ''$SANDBOX_NAME'' not Ready after WhatsApp rebuild (list: ${sandbox_list:0:200})' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA7a: WhatsApp credential-like env var found in sandbox environment' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA7a: No WhatsApp credential-like env var present in sandbox environment' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA7b: WhatsApp credential placeholder found in sandbox process list' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA7b: No WhatsApp credential placeholder present in sandbox process list' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA7c: WhatsApp host credential material found on sandbox filesystem: ${sandbox_fs_wa}' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA7c: No WhatsApp host credential material found on sandbox filesystem' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA8: WhatsApp account is enabled in openclaw.json' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA8: WhatsApp account missing or disabled in openclaw.json (${whatsapp_account_json:0:200})' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA8a: WhatsApp health monitor is disabled for unpaired QR session' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA8a: WhatsApp health monitor is not disabled (${whatsapp_account_json:0:200})' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA9: WhatsApp config has no token/auth/session provider placeholders' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support + - legacy: 'M-WA9: WhatsApp config contains secret-like fields: ${whatsapp_secret_fields}' + status: deferred + reason: live WhatsApp QR-only behavior requires non-deterministic infrastructure; retained for bucket parity tracking + owner: e2e-maintainers + runner_requirement: sandbox runner with WhatsApp QR channel support test-network-policy.sh: scenario: ubuntu-repo-cloud-openclaw status: migrated