mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 17:53:39 +00:00
fix(release): qualify frozen candidates with current tooling (#159505)
This commit is contained in:
parent
82fbdbf3db
commit
ea3c488f4f
7 changed files with 204 additions and 7 deletions
25
.github/workflows/ci.yml
vendored
25
.github/workflows/ci.yml
vendored
|
|
@ -2056,6 +2056,25 @@ jobs:
|
|||
});
|
||||
}
|
||||
}
|
||||
// The trusted planner may name owners added after a frozen checkout.
|
||||
// Project them out here so the runner never receives impossible work.
|
||||
const projectFrozenNodeTestPlan = (plan) => {
|
||||
const configs = plan.configs?.filter((config) => existsSync(config));
|
||||
if (plan.configs?.length && !configs?.length) {
|
||||
return null;
|
||||
}
|
||||
return { ...plan, configs };
|
||||
};
|
||||
const targetNodeTestShards = compatibilityTarget
|
||||
? rawNodeTestShards.flatMap((shard) => {
|
||||
const groups = shard.groups
|
||||
?.map(projectFrozenNodeTestPlan)
|
||||
.filter((group) => group !== null);
|
||||
if (shard.groups?.length && !groups?.length) return [];
|
||||
const projected = projectFrozenNodeTestPlan({ ...shard, groups });
|
||||
return projected ? [projected] : [];
|
||||
})
|
||||
: rawNodeTestShards;
|
||||
// Node rows list every striped test file. Current targets pack the
|
||||
// projected runner contract; older targets keep their flat fields or
|
||||
// projected legacy groups because their shard runner predates the codec.
|
||||
|
|
@ -2069,7 +2088,7 @@ jobs:
|
|||
})
|
||||
: null;
|
||||
const nodeTestGroupsCodec =
|
||||
(rawNodeTestShards.length > 0 || uiTestGroups !== null) &&
|
||||
(targetNodeTestShards.length > 0 || uiTestGroups !== null) &&
|
||||
existsSync(nodeTestGroupsCodecPath)
|
||||
? await importTargetPlan(nodeTestGroupsCodecPath)
|
||||
: null;
|
||||
|
|
@ -2120,7 +2139,7 @@ jobs:
|
|||
"test/vitest/vitest.tooling-docker.config.ts",
|
||||
]);
|
||||
// The same capped matrix owns compact and plugin work; admit its longest rows first.
|
||||
const nodeTestShards = rawNodeTestShards
|
||||
const nodeTestShards = targetNodeTestShards
|
||||
.toSorted((a, b) =>
|
||||
Number(b.runner === "runson-c8i-8xlarge") - Number(a.runner === "runson-c8i-8xlarge") ||
|
||||
(b.predictedSeconds ?? 0) - (a.predictedSeconds ?? 0))
|
||||
|
|
@ -2200,7 +2219,7 @@ jobs:
|
|||
!frozenTarget && !compatibilityTarget && !releaseGate &&
|
||||
/^[0-9a-f]{40}$/u.test(checkoutRevision) && checkoutRevision === workflowRevision &&
|
||||
typeof nodeTestPlan.hasCompleteStartupCorpusCoverage === "function" &&
|
||||
nodeTestPlan.hasCompleteStartupCorpusCoverage(rawNodeTestShards, startupCorpusTestFiles)
|
||||
nodeTestPlan.hasCompleteStartupCorpusCoverage(targetNodeTestShards, startupCorpusTestFiles)
|
||||
? checkoutRevision : "";
|
||||
if (startupCorpusNodeRevision) {
|
||||
// The exact-tree Node receipt makes this row's only test step a no-op.
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { packFutureUpdateFixture } from "../update-first-hop-package-fixtures.mj
|
|||
import { observePostCoreCommand } from "./process-observer.mjs";
|
||||
|
||||
// Without a core tarball, run only the plugin reinstall boundary against the supplied CLI.
|
||||
export async function runConsentScenario(entry, coreTarball) {
|
||||
export async function runConsentScenario(entry, coreTarball, options = {}) {
|
||||
assert(entry, "expected CLI entry");
|
||||
let coreTarballSha256;
|
||||
if (coreTarball) {
|
||||
|
|
@ -280,10 +280,27 @@ export async function runConsentScenario(entry, coreTarball) {
|
|||
"accepted forced reinstall replaces package and acceptance while remaining disabled",
|
||||
"explicit enable activates the reviewed replacement",
|
||||
];
|
||||
if (!coreTarball) {
|
||||
if (!coreTarball || options.coreUpdateConsent === false) {
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{ status: "passed", root, assertions: reinstallAssertions, runs, snapshots },
|
||||
{
|
||||
status: "passed",
|
||||
root,
|
||||
assertions: reinstallAssertions,
|
||||
...(coreTarball && options.coreUpdateConsent === false
|
||||
? {
|
||||
omissions: [
|
||||
{
|
||||
scenario: "core-update-consent",
|
||||
reason:
|
||||
"selected frozen target predates the recorded update compatibility contract",
|
||||
},
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
runs,
|
||||
snapshots,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -303,7 +303,10 @@ function assertCorruptPluginPolicyPreserved(configPath, pluginId) {
|
|||
|
||||
const [command, arg, arg2] = process.argv.slice(2);
|
||||
const commands = {
|
||||
consent: () => runConsentScenario(arg, arg2),
|
||||
consent: () =>
|
||||
runConsentScenario(arg, arg2, {
|
||||
coreUpdateConsent: process.env.OPENCLAW_E2E_CORE_UPDATE_CONSENT !== "0",
|
||||
}),
|
||||
seed: seedInstallState,
|
||||
"wait-registry": waitRegistry,
|
||||
snapshot: () => process.stdout.write(JSON.stringify(pluginRecordSnapshot(), null, 2)),
|
||||
|
|
|
|||
|
|
@ -22,12 +22,33 @@ PACKAGE_TGZ="$(docker_e2e_prepare_package_tgz plugin-update "${OPENCLAW_CURRENT_
|
|||
# Bare lanes mount the package artifact instead of baking app sources into the image.
|
||||
docker_e2e_package_mount_args "$PACKAGE_TGZ"
|
||||
|
||||
CORE_UPDATE_CONSENT=1
|
||||
source "$ROOT_DIR/scripts/lib/frozen-target-compat.sh"
|
||||
TARGET_ROOT_DIR="$(cd "${OPENCLAW_DOCKER_E2E_REPO_ROOT:-$ROOT_DIR}" && pwd)"
|
||||
context_status=0
|
||||
openclaw_prepare_frozen_target_context "$TARGET_ROOT_DIR" || context_status=$?
|
||||
case "$context_status" in
|
||||
0)
|
||||
source_status=0
|
||||
openclaw_frozen_target_source_has_path \
|
||||
"$TARGET_ROOT_DIR" scripts/lib/update-compat-contract.mjs || source_status=$?
|
||||
case "$source_status" in
|
||||
0) ;;
|
||||
1) CORE_UPDATE_CONSENT=0 ;;
|
||||
*) exit "$source_status" ;;
|
||||
esac
|
||||
;;
|
||||
1) ;;
|
||||
*) exit "$context_status" ;;
|
||||
esac
|
||||
|
||||
docker_e2e_build_or_reuse "$IMAGE_NAME" plugin-update "$ROOT_DIR/scripts/e2e/Dockerfile" "$ROOT_DIR" "bare" "$SKIP_BUILD"
|
||||
OPENCLAW_TEST_STATE_SCRIPT_B64="$(docker_e2e_test_state_shell_b64 plugin-update empty)"
|
||||
|
||||
echo "Running unchanged plugin update and capability consent smoke..."
|
||||
docker_e2e_run_with_harness \
|
||||
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
|
||||
-e "OPENCLAW_E2E_CORE_UPDATE_CONSENT=$CORE_UPDATE_CONSENT" \
|
||||
-e OPENCLAW_SKIP_CHANNELS=1 \
|
||||
-e OPENCLAW_SKIP_PROVIDERS=1 \
|
||||
-e "OPENCLAW_TEST_STATE_SCRIPT_B64=$OPENCLAW_TEST_STATE_SCRIPT_B64" \
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ export function runCiManifestFixture(options: {
|
|||
nodeRunnerBackend?: "blacksmith" | "github" | "hybrid" | "runson";
|
||||
runnerProfile?: "blacksmith" | "github" | "hybrid";
|
||||
targetHostedRunnerProfileContract?: boolean;
|
||||
targetFiles?: string[];
|
||||
missingTargetFiles?: string[];
|
||||
uiE2eProjectsCapability?: boolean;
|
||||
uiReleaseTier?: boolean;
|
||||
uiRealGatewayShards?: boolean;
|
||||
|
|
@ -81,6 +83,45 @@ export function runCiManifestFixture(options: {
|
|||
try {
|
||||
const scriptsDir = path.join(root, "scripts", "lib");
|
||||
mkdirSync(scriptsDir, { recursive: true });
|
||||
const selectedTargetFiles = new Set(options.targetFiles ?? []);
|
||||
if (!options.nodeTestShards) {
|
||||
selectedTargetFiles.add(
|
||||
options.bundledPlanner
|
||||
? options.runnerBackend === "runson"
|
||||
? "test/vitest/vitest.cron.config.ts"
|
||||
: "test/vitest/bundled.config.ts"
|
||||
: "test/vitest/legacy.config.ts",
|
||||
);
|
||||
}
|
||||
const collectSelectedTargetFiles = (plan: Record<string, unknown>) => {
|
||||
const configs = plan.configs;
|
||||
if (Array.isArray(configs)) {
|
||||
for (const config of configs) {
|
||||
if (typeof config === "string") {
|
||||
selectedTargetFiles.add(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
const groups = plan.groups;
|
||||
if (Array.isArray(groups)) {
|
||||
for (const group of groups) {
|
||||
if (group && typeof group === "object") {
|
||||
collectSelectedTargetFiles(group as Record<string, unknown>);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const shard of options.nodeTestShards ?? []) {
|
||||
collectSelectedTargetFiles(shard);
|
||||
}
|
||||
for (const missing of options.missingTargetFiles ?? []) {
|
||||
selectedTargetFiles.delete(missing);
|
||||
}
|
||||
for (const file of selectedTargetFiles) {
|
||||
const target = path.join(root, file);
|
||||
mkdirSync(path.dirname(target), { recursive: true });
|
||||
writeFileSync(target, "export {};\n");
|
||||
}
|
||||
if (options.bunTestRuntime) {
|
||||
writeFileSync(
|
||||
path.join(scriptsDir, "ci-test-runtime.mts"),
|
||||
|
|
|
|||
|
|
@ -9512,6 +9512,93 @@ describe("ci workflow guards", () => {
|
|||
).toEqual(row.groups);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ label: "verified release candidate", releaseCandidateCompatibility: true },
|
||||
{ label: "unverified manual target", releaseCandidateCompatibility: false },
|
||||
])("projects current-only test owners only for $label", ({ releaseCandidateCompatibility }) => {
|
||||
const currentConfig = "test/vitest/vitest.current.config.ts";
|
||||
const currentTest = "src/current.test.ts";
|
||||
const missingConfig = "test/vitest/vitest.future.config.ts";
|
||||
const missingTest = "src/future.test.ts";
|
||||
const result = runCiManifestFixture({
|
||||
bundledPlanner: true,
|
||||
historicalCompatibility: false,
|
||||
missingTargetFiles: [missingConfig],
|
||||
nodeTestGroupsCodec: false,
|
||||
releaseCandidateCompatibility,
|
||||
targetFiles: [currentConfig],
|
||||
nodeTestShards: [
|
||||
{
|
||||
checkName: "mixed-flat",
|
||||
configs: [currentConfig, missingConfig],
|
||||
includePatterns: [currentTest, missingTest, "src/**/*.integration.test.ts"],
|
||||
requiresDist: false,
|
||||
runner: "ubuntu-24.04",
|
||||
shardName: "mixed-flat",
|
||||
},
|
||||
{
|
||||
checkName: "missing-flat",
|
||||
configs: [missingConfig],
|
||||
includePatterns: [missingTest],
|
||||
requiresDist: false,
|
||||
runner: "ubuntu-24.04",
|
||||
shardName: "missing-flat",
|
||||
},
|
||||
{
|
||||
checkName: "mixed-groups",
|
||||
groups: [
|
||||
{
|
||||
configs: [currentConfig, missingConfig],
|
||||
includePatterns: [currentTest, missingTest],
|
||||
shard_name: "current-group",
|
||||
},
|
||||
{
|
||||
configs: [missingConfig],
|
||||
includePatterns: [missingTest],
|
||||
shard_name: "missing-group",
|
||||
},
|
||||
],
|
||||
requiresDist: false,
|
||||
runner: "ubuntu-24.04",
|
||||
shardName: "mixed-groups",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(result.status, result.output).toBe(0);
|
||||
const rows = JSON.parse(
|
||||
expectDefined(result.outputs.checks_node_core_nondist_matrix, "frozen Node matrix"),
|
||||
).include;
|
||||
expect(rows.map((row: { check_name: string }) => row.check_name)).toEqual(
|
||||
releaseCandidateCompatibility
|
||||
? ["mixed-flat", "mixed-groups"]
|
||||
: ["mixed-flat", "missing-flat", "mixed-groups"],
|
||||
);
|
||||
expect(rows[0].configs).toEqual(
|
||||
releaseCandidateCompatibility ? [currentConfig] : [currentConfig, missingConfig],
|
||||
);
|
||||
expect(rows[0].includePatterns).toEqual([
|
||||
currentTest,
|
||||
missingTest,
|
||||
"src/**/*.integration.test.ts",
|
||||
]);
|
||||
const groupedRow = releaseCandidateCompatibility ? rows[1] : rows[2];
|
||||
expect(groupedRow.groups).toEqual([
|
||||
expect.objectContaining({
|
||||
configs: releaseCandidateCompatibility ? [currentConfig] : [currentConfig, missingConfig],
|
||||
includePatterns: [currentTest, missingTest],
|
||||
shard_name: "current-group",
|
||||
}),
|
||||
...(releaseCandidateCompatibility
|
||||
? []
|
||||
: [
|
||||
expect.objectContaining({
|
||||
configs: [missingConfig],
|
||||
shard_name: "missing-group",
|
||||
}),
|
||||
]),
|
||||
]);
|
||||
});
|
||||
|
||||
it("provisions ripgrep for real filesystem contract selections", () => {
|
||||
const contract = "src/agents/filesystem-tools-output-contract.test.ts";
|
||||
const nativeTools = "src/agents/sessions/tools/index.test.ts";
|
||||
|
|
|
|||
|
|
@ -288,6 +288,15 @@ describe("installed-CLI consent scenario report contract", () => {
|
|||
expect(fs.existsSync(path.join(installPath, "index.js"))).toBe(true);
|
||||
});
|
||||
|
||||
it("retains plugin consent proof when a frozen target predates core update consent", async () => {
|
||||
await expect(
|
||||
runConsentScenario(entry, coreTarball, { coreUpdateConsent: false }),
|
||||
).resolves.toBeUndefined();
|
||||
expect(installed).toBe(2);
|
||||
expect(adapters.future).not.toHaveBeenCalled();
|
||||
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('"core-update-consent"'));
|
||||
});
|
||||
|
||||
it.each([
|
||||
[
|
||||
"missing plugin warning",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue