mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-04 10:10:01 +00:00
fix(e2e): stage candidate providers for legacy-operator survivor lanes
Legacy published baselines (2026.7.35, 2026.6.34) author plugins.allow from their bundled provider inventory. The candidate's doctor restores those now-external providers at the exact candidate version, which does not exist on npm or ClawHub before publication, so the prepublish registry must carry them. Stage every official provider from the selected candidate's external provider catalog for legacy-operator-state lanes, loaded once per plan.
This commit is contained in:
parent
6188881e05
commit
ac59199ad3
2 changed files with 98 additions and 7 deletions
|
|
@ -21,6 +21,8 @@ import {
|
|||
type DockerE2eReleaseProfileInput,
|
||||
} from "./docker-e2e-scenarios.mts";
|
||||
import officialExternalChannelCatalog from "./official-external-channel-catalog.json" with { type: "json" };
|
||||
import officialExternalProviderCatalog from "./official-external-provider-catalog.json" with { type: "json" };
|
||||
import { isRecord } from "./record-shared.mjs";
|
||||
import {
|
||||
UPDATE_FIRST_HOP_COMPAT_LANE,
|
||||
isUpdateFirstHopCompatLane,
|
||||
|
|
@ -758,9 +760,43 @@ function upgradeSurvivorBaselineVersionForLane(poolLane: DockerE2eLane): string
|
|||
return /(?:^|\/|@)(\d{4}\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/u.exec(spec ?? "")?.[1] ?? null;
|
||||
}
|
||||
|
||||
export function requiredPrepublishPluginPackagesForLanes(poolLanes: DockerE2eLane[]): string[] {
|
||||
function legacyOperatorProviderPackages(
|
||||
targetRoot?: string,
|
||||
frozenTarget?: InertTargetContract,
|
||||
): string[] {
|
||||
const catalogPath = "scripts/lib/official-external-provider-catalog.json";
|
||||
const text =
|
||||
targetRoot || frozenTarget ? readTargetMetadata(targetRoot, catalogPath, frozenTarget) : null;
|
||||
// Older candidates without the external catalog still carry their bundled providers.
|
||||
const catalog: unknown =
|
||||
targetRoot || frozenTarget
|
||||
? text === null
|
||||
? { entries: [] }
|
||||
: JSON.parse(text)
|
||||
: officialExternalProviderCatalog;
|
||||
if (!isRecord(catalog) || !Array.isArray(catalog.entries)) {
|
||||
throw new Error(`invalid candidate provider catalog: ${catalogPath}`);
|
||||
}
|
||||
return catalog.entries.flatMap((entry: unknown) =>
|
||||
isRecord(entry) &&
|
||||
entry.source === "official" &&
|
||||
typeof entry.name === "string" &&
|
||||
isRecord(entry.openclaw) &&
|
||||
isRecord(entry.openclaw.install) &&
|
||||
entry.openclaw.install.npmSpec === entry.name
|
||||
? [entry.name]
|
||||
: [],
|
||||
);
|
||||
}
|
||||
|
||||
export function requiredPrepublishPluginPackagesForLanes(
|
||||
poolLanes: DockerE2eLane[],
|
||||
targetRoot?: string,
|
||||
frozenTarget?: InertTargetContract,
|
||||
): string[] {
|
||||
const configuredChannelIds = new Set<string>();
|
||||
const requiredPackages = new Set<string>();
|
||||
let legacyOperatorProviders: string[] | undefined;
|
||||
for (const poolLane of poolLanes) {
|
||||
for (const packageName of poolLane.prepublishPluginPackages ?? []) {
|
||||
requiredPackages.add(packageName);
|
||||
|
|
@ -783,6 +819,12 @@ export function requiredPrepublishPluginPackagesForLanes(poolLanes: DockerE2eLan
|
|||
requiredPackages.add("@openclaw/codex");
|
||||
requiredPackages.add("@openclaw/discord");
|
||||
requiredPackages.add("@openclaw/duckduckgo-plugin");
|
||||
// The baseline authors an allowlist from its enabled bundled inventory.
|
||||
// Supply candidate providers once, even when several baselines share the registry.
|
||||
legacyOperatorProviders ??= legacyOperatorProviderPackages(targetRoot, frozenTarget);
|
||||
for (const packageName of legacyOperatorProviders) {
|
||||
requiredPackages.add(packageName);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
for (const packageName of UPGRADE_SURVIVOR_RUNTIME_COMPANION_PACKAGES) {
|
||||
|
|
@ -836,12 +878,18 @@ function buildPlanJson(params: {
|
|||
releaseChunk: string;
|
||||
releaseProfile: DockerE2eReleaseProfile;
|
||||
selectedLaneNames: string[];
|
||||
targetRoot?: string;
|
||||
frozenTarget?: InertTargetContract;
|
||||
}) {
|
||||
const scheduledLanes = [...params.orderedLanes, ...params.orderedTailLanes];
|
||||
const imageKinds = unique(scheduledLanes.map((poolLane) => poolLane.e2eImageKind)).toSorted(
|
||||
(a, b) => a.localeCompare(b),
|
||||
);
|
||||
const requiredPrepublishPluginPackages = requiredPrepublishPluginPackagesForLanes(scheduledLanes);
|
||||
const requiredPrepublishPluginPackages = requiredPrepublishPluginPackagesForLanes(
|
||||
scheduledLanes,
|
||||
params.targetRoot,
|
||||
params.frozenTarget,
|
||||
);
|
||||
return {
|
||||
chunk: params.releaseChunk || undefined,
|
||||
credentials: unique(scheduledLanes.flatMap(laneCredentialRequirements)).toSorted((a, b) =>
|
||||
|
|
@ -1058,6 +1106,8 @@ export function resolveDockerE2ePlan(options: DockerE2ePlanOptions) {
|
|||
releaseChunk: options.releaseChunk,
|
||||
releaseProfile,
|
||||
selectedLaneNames: options.selectedLaneNames,
|
||||
targetRoot: options.upgradeSurvivorTargetRoot,
|
||||
frozenTarget: options.frozenTarget,
|
||||
}),
|
||||
scheduledLanes: [...orderedLanes, ...orderedTailLanes],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1510,11 +1510,16 @@ await import('./scripts/check-docker-e2e-boundaries.mts');`,
|
|||
"published-upgrade-survivor-2026.9.4-legacy-operator-state",
|
||||
"published-upgrade-survivor-2026.9.6-legacy-operator-state",
|
||||
]);
|
||||
expect(plan.requiredPrepublishPluginPackages).toEqual([
|
||||
"@openclaw/codex",
|
||||
"@openclaw/discord",
|
||||
"@openclaw/duckduckgo-plugin",
|
||||
]);
|
||||
expect(plan.requiredPrepublishPluginPackages).toEqual(
|
||||
expect.arrayContaining([
|
||||
"@openclaw/codex",
|
||||
"@openclaw/discord",
|
||||
"@openclaw/duckduckgo-plugin",
|
||||
"@openclaw/byteplus-provider",
|
||||
"@openclaw/voyage-provider",
|
||||
]),
|
||||
);
|
||||
expect(plan.requiredPrepublishPluginPackages).not.toContain("@telnyx/openclaw-provider");
|
||||
expect(plan.lanes.every((lane) => lane.weight === 3)).toBe(true);
|
||||
for (const alias of ["reported-issues", "far-reaching"]) {
|
||||
expect(
|
||||
|
|
@ -1527,6 +1532,42 @@ await import('./scripts/check-docker-e2e-boundaries.mts');`,
|
|||
}
|
||||
});
|
||||
|
||||
it("stages legacy operator providers from the selected candidate catalog", () => {
|
||||
const entry = (name: string, source = "official") => ({
|
||||
name,
|
||||
source,
|
||||
openclaw: { install: { npmSpec: name } },
|
||||
});
|
||||
const plan = planFor({
|
||||
selectedLaneNames: ["published-upgrade-survivor"],
|
||||
upgradeSurvivorBaselines: "2026.7.35 2026.6.34",
|
||||
upgradeSurvivorScenarios: "legacy-operator-state",
|
||||
frozenTarget: {
|
||||
mode: "inert",
|
||||
source: {
|
||||
readText: (relativePath) =>
|
||||
relativePath === "scripts/lib/official-external-provider-catalog.json"
|
||||
? JSON.stringify({
|
||||
entries: [
|
||||
entry("@openclaw/candidate-provider"),
|
||||
entry("@vendor/provider", "external"),
|
||||
],
|
||||
})
|
||||
: existsSync(relativePath)
|
||||
? readFileSync(relativePath, "utf8")
|
||||
: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(plan.requiredPrepublishPluginPackages).toEqual([
|
||||
"@openclaw/candidate-provider",
|
||||
"@openclaw/codex",
|
||||
"@openclaw/discord",
|
||||
"@openclaw/duckduckgo-plugin",
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["workshop-doctor-recovery", "2026.9.4", "2026.9.3 2026.9.4 2026.9.5"],
|
||||
["update-report-recovery", "2026.9.6", "2026.9.5 2026.9.6 2026.9.7"],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue