fix(release): repair 2026.9.6 plugin shard and FaceTime manifest validation (#155892)

## Problem

Full Release Validation run 35760444831 for 2026.9.6 fails two release-only lanes that regular PR CI never runs:

1. `checks-node-agentic-plugins` (Plugin Prerelease, `test/vitest/vitest.plugins.config.ts`): 12 files / 40 tests in `src/plugins/` fail with symptoms such as `Plugin fixture-speech capabilityCatalogEntry failed: Unexpected token 'export'`, `expected { values: [ undefined, 'javascript' ] } to match object { values: [ 'typescript', ... ] }`, and `expected undefined to be 'overloaded'`, alongside Node warnings `Failed to load the ES module: .../capability-catalog.js`.
2. `extensions shard 8`: `extensions/facetime/tests/package-manifest.test.ts` fails once the release version controller bumps the package to 2026.9.6 (`minHostVersion` expected `>=2026.9.6`, received `>=2026.9.4`).

## Root causes

**Plugins shard.** #153508 added a `--import tsx/esm` `execArgv` preload to every Node Vitest fork worker (`test/vitest/vitest.shared.config.ts`). The `src/plugins` suites exercise the plugin loader's own native `require()`/`import()` of `.ts`/`.js` fixtures (TS/JS peers, CJS/ESM interop, built capability catalogs, public surfaces). With the tsx hooks installed in the worker, Node's own module semantics under test change: a `.ts` peer in a `commonjs` package requires as an empty namespace, ESM `.js` catalogs in fixture packages fall through the CJS path, and loader ownership assertions fail. The shard is `RELEASE_ONLY_PLUGIN_SHARDS`, so the breakage surfaced only in Full Release Validation. A standalone probe (`createRequire(...)("./native.cjs")` requiring `peer.ts` + `peer.js`) returns `["typescript","javascript"]` on plain Node 24 and 26 and `[null,"javascript"]` with `--import scripts/tsx.mjs`.

**FaceTime manifest.** #155818 derived every host-contract field from `package.json#version`, but `scripts/sync-plugin-versions.ts` deliberately leaves `openclaw.install.minHostVersion` alone ("a compatibility floor, not release alignment metadata") while rewriting `peerDependencies.openclaw`, `compat.pluginApi`, and `build.openclawVersion`. The derived expectation therefore breaks on every release bump.

## Solution

- `test/vitest/vitest.scoped-config.ts`: accept an `execArgv` override.
- `test/vitest/vitest.plugins.config.ts`: split the shard into two inline projects (same pattern as `vitest.gateway-server.config.ts`): `plugins` keeps the shared preload (its suites load bundled plugins from source and need tsx's `.js` to `.ts` SDK resolution), and `plugins-native-loader` runs the eight loader-semantics suites (`plugin-module-generation*`, `capability-provider-runtime.generation`, `bundled-plugin-metadata.public-surfaces`, `manifest-registry`, `public-surface-loader`, `plugin-runtime-artifact-resolution`, `stage-bundled-plugin-runtime`) with `execArgv: []`. The root config keeps the complete inventory so planner discovery and `--project` routing are unchanged.
- `extensions/facetime/tests/package-manifest.test.ts`: expect the literal `>=2026.9.4` floor for `install.minHostVersion`; the controller-synced fields stay derived from the package version.

## Impact

Release-only validation lanes match what the runtime does in production for the loader suites (no tsx hooks in a Gateway process). No product code changes. Regular CI shards are unaffected; only the eight native-loader suites drop the preload.

## Evidence

- Baseline, exact job command (`pnpm test -- test/vitest/vitest.plugins.config.ts --`, Node 24.21.0, `OPENCLAW_VITEST_MAX_WORKERS=2`): 13 failed files / 41 failed tests (CI's 12 plus `plugin-graceful-init-failure`), matching FRV run 35760444831 job `checks-node-agentic-plugins` (also red in runs 35743778013 and 35761282770).
- Planner/routing owners with the split config: `test/scripts/ci-node-test-plan.test.ts`, `test-projects.test.ts`, `test-projects-routing.test.ts`, `plugin-prerelease-test-plan.test.ts`: 1811/1811 pass.
- Fixed, same command and Node: 5 failed files / 10 failed tests, 375 passed files / 6101 passed tests. All eight loader-semantics files are green. Dropping the preload for the whole shard instead (probe run) fixes the same eight files but breaks 11 other files on `Cannot find module '.../src/agents/defaults.js' imported from .../src/plugin-sdk/provider-model-metadata.ts`, which is why the shard is split rather than de-preloaded.
- Remaining red files are independent of the preload (identical failures with and without it, and with `providers.runtime-core.ts`, `provider-hook-runtime-core.ts`, `plugin-instance*.ts`, `host-hook-state.ts` reverted to their pre-#155711/#155250/#154625/#155714 versions): `provider-runtime.test.ts` (6: `resolveProviderPluginsForHooks`/`classifyProviderFailoverSignalWithPlugin` no longer read the `withPluginRuntimeRegistryScope` registry), `native-session-catalog-registration.test.ts` (1: abort surfaces `PluginInstanceUnavailableError` instead of the caller's retirement error), `plugin-instance-invocation.test.ts` (1), `provider-public-artifacts.test.ts` (1: listener count after bundled policy load), `plugin-graceful-init-failure.test.ts` (1: `Maximum call stack size exceeded`, local only). These are product/test contract drift from the plugin runtime refactors and need their owners; they are called out in the landing report.
- `extensions/facetime/tests/package-manifest.test.ts`: 2/2 pass at 2026.9.4 and 2/2 pass after a local `pnpm release:prepare -- --version 2026.9.6 --write` (bump reverted, not committed).
- `OPENCLAW_TESTBOX=1 pnpm check:changed`: in progress at PR creation; result recorded in the landing report.

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Peter Steinberger 2026-09-22 12:22:16 -07:00 • committed by GitHub
parent 51991acb18
commit c8a045b2c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 53 additions and 4 deletions

View file

@ -91,7 +91,7 @@ describe("FaceTime plugin manifest", () => {
clawhubSpec: "clawhub:@openclaw/facetime",
npmSpec: "@openclaw/facetime",
defaultChoice: "npm",
minHostVersion: `>=${hostVersion}`,
minHostVersion: ">=2026.9.4",
allowInvalidConfigRecovery: true,
});
expect(packageManifest.openclaw.compat.pluginApi).toBe(`>=${hostVersion}`);

View file

@ -1,9 +1,26 @@
// Vitest plugins config wires the plugins test shard.
import { defineConfig } from "vitest/config";
import { databaseWorkerCoreTestFiles } from "./vitest.database-worker-core-paths.mjs";
import { relativizeScopedPatterns } from "./vitest.pattern-file.ts";
import { createScopedVitestConfig } from "./vitest.scoped-config.ts";
// These suites exercise the plugin loader's own native require()/import()
// semantics on fixture packages. The shared tsx/esm worker preload rewrites
// .ts and ambiguous .js loading in-process and would replace the runtime under
// test, so they run in Node workers without it.
export const nativeLoaderPluginTestFiles = [
"src/plugins/bundled-plugin-metadata.public-surfaces.test.ts",
"src/plugins/capability-provider-runtime.generation.test.ts",
"src/plugins/manifest-registry.test.ts",
"src/plugins/plugin-module-generation.interop.test.ts",
"src/plugins/plugin-module-generation.test.ts",
"src/plugins/plugin-runtime-artifact-resolution.test.ts",
"src/plugins/public-surface-loader.test.ts",
"src/plugins/stage-bundled-plugin-runtime.test.ts",
];
export function createPluginsVitestConfig(env?: Record<string, string | undefined>) {
return createScopedVitestConfig(["src/plugins/**/*.test.ts"], {
const options = {
dir: "src/plugins",
env,
exclude: [
@ -12,10 +29,40 @@ export function createPluginsVitestConfig(env?: Record<string, string | undefine
...databaseWorkerCoreTestFiles,
],
isolate: true,
name: "plugins",
// Runtime ingress uses the application main-thread SQLite worker broker.
pool: "forks",
pool: "forks" as const,
passWithNoTests: true,
};
const config = createScopedVitestConfig(["src/plugins/**/*.test.ts"], {
...options,
name: "plugins",
});
const nativeLoader = createScopedVitestConfig(nativeLoaderPluginTestFiles, {
...options,
execArgv: [],
intersectIncludeFile: true,
name: "plugins-native-loader",
});
return defineConfig({
...config,
test: {
...config.test,
// Config discovery retains the complete inventory; only the inline leaves run.
projects: [
{
...config,
extends: false,
test: {
...config.test,
exclude: [
...(config.test?.exclude ?? []),
...relativizeScopedPatterns(nativeLoaderPluginTestFiles, options.dir),
],
},
},
{ ...nativeLoader, extends: false },
],
},
});
}

View file

@ -183,6 +183,7 @@ export function createScopedVitestConfig(
env?: Record<string, string | undefined>;
environment?: string;
exclude?: string[];
execArgv?: string[];
argv?: string[];
includeOpenClawRuntimeSetup?: boolean;
isolate?: boolean;
@ -245,6 +246,7 @@ export function createScopedVitestConfig(
...(options?.deps ? { deps: options.deps } : {}),
...(options?.name ? { name: options.name } : {}),
...(options?.environment ? { environment: options.environment } : {}),
...(options?.execArgv ? { execArgv: options.execArgv } : {}),
isolate,
...(runner ? { runner } : { runner: undefined }),
setupFiles,