diff --git a/docs/help/testing-updates-plugins.md b/docs/help/testing-updates-plugins.md index fcaa7fa028f..45ab2115d8e 100644 --- a/docs/help/testing-updates-plugins.md +++ b/docs/help/testing-updates-plugins.md @@ -123,8 +123,8 @@ pnpm test:docker:published-upgrade-survivor ``` Available scenarios are `base`, `feishu-channel`, `bootstrap-persona`, -`plugin-deps-cleanup`, `configured-plugin-installs`, `tilde-log-path`, and -`versioned-runtime-deps`. In aggregate runs, +`plugin-deps-cleanup`, `configured-plugin-installs`, +`stale-source-plugin-shadow`, `tilde-log-path`, and `versioned-runtime-deps`. In aggregate runs, `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS=reported-issues` expands to all reported issue-shaped scenarios, including the configured-plugin install migration. diff --git a/docs/reference/test.md b/docs/reference/test.md index 13d3cbb68ee..f20d60f73e3 100644 --- a/docs/reference/test.md +++ b/docs/reference/test.md @@ -44,7 +44,7 @@ title: "Tests" - `pnpm test:docker:openwebui`: Starts Dockerized OpenClaw + Open WebUI, signs in through Open WebUI, checks `/api/models`, then runs a real proxied chat through `/api/chat/completions`. Requires a usable live model key (for example OpenAI in `~/.profile`), pulls an external Open WebUI image, and is not expected to be CI-stable like the normal unit/e2e suites. - `pnpm test:docker:mcp-channels`: Starts a seeded Gateway container and a second client container that spawns `openclaw mcp serve`, then verifies routed conversation discovery, transcript reads, attachment metadata, live event queue behavior, outbound send routing, and Claude-style channel + permission notifications over the real stdio bridge. The Claude notification assertion reads the raw stdio MCP frames directly so the smoke reflects what the bridge actually emits. - `pnpm test:docker:upgrade-survivor`: Installs the packed OpenClaw tarball over a dirty old-user fixture, runs package update plus non-interactive doctor without live provider or channel keys, then starts a loopback Gateway and checks that agents, channel config, plugin allowlists, workspace/session files, stale legacy plugin dependency state, startup, and RPC status survive. -- `pnpm test:docker:published-upgrade-survivor`: Installs `openclaw@latest` by default, seeds realistic existing-user files without live provider or channel keys, configures that baseline with a baked `openclaw config set` command recipe, updates that published install to the packed OpenClaw tarball, runs non-interactive doctor, writes `.artifacts/upgrade-survivor/summary.json`, then starts a loopback Gateway and checks that configured intents, workspace/session files, stale plugin config and legacy dependency state, startup, `/healthz`, `/readyz`, and RPC status survive or repair cleanly. Override one baseline with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC`, expand an exact matrix with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS` such as `all-since-2026.4.23`, or add scenario fixtures with `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS=reported-issues`; the reported-issues set includes `configured-plugin-installs` to verify configured external OpenClaw plugins install automatically during upgrade. Package Acceptance exposes those as `published_upgrade_survivor_baseline`, `published_upgrade_survivor_baselines`, and `published_upgrade_survivor_scenarios`. +- `pnpm test:docker:published-upgrade-survivor`: Installs `openclaw@latest` by default, seeds realistic existing-user files without live provider or channel keys, configures that baseline with a baked `openclaw config set` command recipe, updates that published install to the packed OpenClaw tarball, runs non-interactive doctor, writes `.artifacts/upgrade-survivor/summary.json`, then starts a loopback Gateway and checks that configured intents, workspace/session files, stale plugin config and legacy dependency state, startup, `/healthz`, `/readyz`, and RPC status survive or repair cleanly. Override one baseline with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC`, expand an exact matrix with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS` such as `all-since-2026.4.23`, or add scenario fixtures with `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS=reported-issues`; the reported-issues set includes `configured-plugin-installs` to verify configured external OpenClaw plugins install automatically during upgrade and `stale-source-plugin-shadow` to keep source-only plugin shadows from breaking startup. Package Acceptance exposes those as `published_upgrade_survivor_baseline`, `published_upgrade_survivor_baselines`, and `published_upgrade_survivor_scenarios`. - `pnpm test:docker:update-migration`: Runs the published-upgrade survivor harness in the cleanup-heavy `plugin-deps-cleanup` scenario, starting at `openclaw@2026.4.23` by default. The separate `Update Migration` workflow expands this lane with `baselines=all-since-2026.4.23` so every stable published package from `.23` onward updates to the candidate and proves configured-plugin dependency cleanup outside Full Release CI. - `pnpm test:docker:plugins`: Runs install/update smoke for local path, `file:`, npm registry packages with hoisted dependencies, git moving refs, ClawHub fixtures, marketplace updates, and Claude-bundle enable/inspect. diff --git a/scripts/e2e/lib/upgrade-survivor/assertions.mjs b/scripts/e2e/lib/upgrade-survivor/assertions.mjs index fd008e1cf72..2e2c1d7fa3d 100644 --- a/scripts/e2e/lib/upgrade-survivor/assertions.mjs +++ b/scripts/e2e/lib/upgrade-survivor/assertions.mjs @@ -8,6 +8,7 @@ const SCENARIOS = new Set([ "bootstrap-persona", "plugin-deps-cleanup", "configured-plugin-installs", + "stale-source-plugin-shadow", "tilde-log-path", "versioned-runtime-deps", ]); @@ -355,6 +356,13 @@ function assertStateSurvived() { assert(actual === contents, `${fileName} was changed during update/doctor`); } } + if (scenario === "stale-source-plugin-shadow") { + const staleRoot = path.join(stateDir, "extensions", "opik-openclaw"); + assert( + fs.existsSync(path.join(staleRoot, "src", "index.ts")), + "source-only plugin shadow fixture missing", + ); + } if (scenario === "versioned-runtime-deps") { if (stage === "baseline") { return; diff --git a/scripts/e2e/lib/upgrade-survivor/run.sh b/scripts/e2e/lib/upgrade-survivor/run.sh index 61a1734f0fc..3f64479651f 100644 --- a/scripts/e2e/lib/upgrade-survivor/run.sh +++ b/scripts/e2e/lib/upgrade-survivor/run.sh @@ -286,6 +286,47 @@ configured_plugin_installs_enabled() { [ "$SCENARIO" = "configured-plugin-installs" ] } +source_only_plugin_shadow_enabled() { + [ "$SCENARIO" = "stale-source-plugin-shadow" ] +} + +seed_source_only_plugin_shadow() { + source_only_plugin_shadow_enabled || return 0 + + local shadow_root="$OPENCLAW_STATE_DIR/extensions/opik-openclaw" + mkdir -p "$shadow_root/src" + cat >"$shadow_root/package.json" <<'JSON' +{ + "name": "@opik/opik-openclaw", + "version": "0.0.0-upgrade-survivor", + "openclaw": { + "extensions": ["./src/index.ts"] + } +} +JSON + cat >"$shadow_root/openclaw.plugin.json" <<'JSON' +{ + "id": "opik-openclaw", + "activation": { + "onStartup": false + }, + "configSchema": { + "type": "object", + "additionalProperties": false, + "properties": {} + } +} +JSON + cat >"$shadow_root/src/index.ts" <<'TS' +export default { + id: "opik-openclaw", + name: "Source-only Opik shadow", + register() {}, +}; +TS + echo "Seeded source-only plugin shadow: $shadow_root" +} + configure_configured_plugin_install_fixture_registry() { configured_plugin_installs_enabled || return 0 @@ -785,6 +826,7 @@ phase validate-baseline-config validate_baseline_config phase install-baseline-plugin-dependencies install_baseline_plugin_dependencies phase seed-legacy-plugin-dependency-debris seed_legacy_plugin_dependency_debris phase assert-legacy-plugin-dependency-debris assert_legacy_plugin_dependency_debris_present +phase seed-source-only-plugin-shadow seed_source_only_plugin_shadow phase assert-baseline assert_baseline_state phase seed-legacy-runtime-deps-symlink seed_legacy_runtime_deps_symlink phase resolve-candidate resolve_candidate_version diff --git a/scripts/lib/docker-e2e-plan.mjs b/scripts/lib/docker-e2e-plan.mjs index b8de69eb664..7d5db1dbf70 100644 --- a/scripts/lib/docker-e2e-plan.mjs +++ b/scripts/lib/docker-e2e-plan.mjs @@ -75,6 +75,7 @@ const UPGRADE_SURVIVOR_SCENARIOS = [ "bootstrap-persona", "plugin-deps-cleanup", "configured-plugin-installs", + "stale-source-plugin-shadow", "tilde-log-path", "versioned-runtime-deps", ]; diff --git a/test/scripts/docker-e2e-plan.test.ts b/test/scripts/docker-e2e-plan.test.ts index d4160b16557..c5b5cabf24a 100644 --- a/test/scripts/docker-e2e-plan.test.ts +++ b/test/scripts/docker-e2e-plan.test.ts @@ -382,6 +382,7 @@ describe("scripts/lib/docker-e2e-plan", () => { "published-upgrade-survivor-2026.4.29-bootstrap-persona", "published-upgrade-survivor-2026.4.29-plugin-deps-cleanup", "published-upgrade-survivor-2026.4.29-configured-plugin-installs", + "published-upgrade-survivor-2026.4.29-stale-source-plugin-shadow", "published-upgrade-survivor-2026.4.29-tilde-log-path", "published-upgrade-survivor-2026.4.29-versioned-runtime-deps", ]); @@ -400,12 +401,14 @@ describe("scripts/lib/docker-e2e-plan", () => { "published-upgrade-survivor-2026.4.29-bootstrap-persona", "published-upgrade-survivor-2026.4.29-plugin-deps-cleanup", "published-upgrade-survivor-2026.4.29-configured-plugin-installs", + "published-upgrade-survivor-2026.4.29-stale-source-plugin-shadow", "published-upgrade-survivor-2026.4.29-tilde-log-path", "published-upgrade-survivor-2026.4.29-versioned-runtime-deps", "published-upgrade-survivor-2026.3.13", "published-upgrade-survivor-2026.3.13-feishu-channel", "published-upgrade-survivor-2026.3.13-bootstrap-persona", "published-upgrade-survivor-2026.3.13-configured-plugin-installs", + "published-upgrade-survivor-2026.3.13-stale-source-plugin-shadow", "published-upgrade-survivor-2026.3.13-tilde-log-path", "published-upgrade-survivor-2026.3.13-versioned-runtime-deps", ]); diff --git a/test/scripts/package-acceptance-workflow.test.ts b/test/scripts/package-acceptance-workflow.test.ts index 81b9ca8edfb..666a3d05339 100644 --- a/test/scripts/package-acceptance-workflow.test.ts +++ b/test/scripts/package-acceptance-workflow.test.ts @@ -253,6 +253,11 @@ describe("package artifact reuse", () => { expect(publishedUpgradeSurvivor).toContain( "assert_legacy_plugin_dependency_debris_before_doctor", ); + expect(publishedUpgradeSurvivor.indexOf("phase seed-source-only-plugin-shadow")).toBeLessThan( + publishedUpgradeSurvivor.indexOf("phase assert-baseline"), + ); + expect(publishedUpgradeSurvivor).toContain('"id": "opik-openclaw"'); + expect(publishedUpgradeSurvivor).toContain('"configSchema": {'); expect(publishedUpgradeSurvivor).toContain( "Legacy plugin dependency debris was already removed before doctor", );