mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 04:40:43 +00:00
ci: shard package upgrade survivor baselines
This commit is contained in:
@@ -861,36 +861,24 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
groups_json: ${{ steps.plan.outputs.groups_json }}
|
||||
groups_json: ${{ steps.groups.outputs.groups_json }}
|
||||
steps:
|
||||
- name: Plan targeted Docker lane groups
|
||||
id: plan
|
||||
- name: Checkout trusted release harness
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Build targeted Docker lane groups
|
||||
id: groups
|
||||
shell: bash
|
||||
env:
|
||||
LANES: ${{ inputs.docker_lanes }}
|
||||
GROUP_SIZE: ${{ inputs.targeted_docker_lane_group_size }}
|
||||
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ inputs.published_upgrade_survivor_baselines }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
groups_json="$(
|
||||
LANES="$LANES" GROUP_SIZE="$GROUP_SIZE" node <<'NODE'
|
||||
const lanes = [...new Set(String(process.env.LANES || "").split(/[,\s]+/u).map((lane) => lane.trim()).filter(Boolean))];
|
||||
if (lanes.length === 0) {
|
||||
throw new Error("docker_lanes is required when planning targeted Docker lane groups.");
|
||||
}
|
||||
const rawGroupSize = Number.parseInt(process.env.GROUP_SIZE || "1", 10);
|
||||
const groupSize = Number.isFinite(rawGroupSize) && rawGroupSize > 0 ? rawGroupSize : 1;
|
||||
const sanitize = (lane) => lane.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "targeted";
|
||||
const groups = [];
|
||||
for (let index = 0; index < lanes.length; index += groupSize) {
|
||||
const groupLanes = lanes.slice(index, index + groupSize);
|
||||
const first = sanitize(groupLanes[0]);
|
||||
const last = sanitize(groupLanes[groupLanes.length - 1]);
|
||||
const label = groupLanes.length === 1 ? first : `${first}--${last}`;
|
||||
groups.push({ label, docker_lanes: groupLanes.join(" ") });
|
||||
}
|
||||
process.stdout.write(JSON.stringify(groups));
|
||||
NODE
|
||||
)"
|
||||
groups_json="$(node scripts/plan-targeted-docker-lane-groups.mjs)"
|
||||
echo "groups_json=${groups_json}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
validate_docker_lanes:
|
||||
@@ -957,7 +945,7 @@ jobs:
|
||||
OPENCLAW_DOCKER_E2E_SELECTED_SHA: ${{ needs.validate_selected_ref.outputs.selected_sha }}
|
||||
OPENCLAW_CURRENT_PACKAGE_TGZ: .artifacts/docker-e2e-package/openclaw-current.tgz
|
||||
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC: ${{ inputs.published_upgrade_survivor_baseline }}
|
||||
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ inputs.published_upgrade_survivor_baselines }}
|
||||
OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ matrix.group.published_upgrade_survivor_baselines || inputs.published_upgrade_survivor_baselines }}
|
||||
OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS: ${{ inputs.published_upgrade_survivor_scenarios }}
|
||||
OPENCLAW_SKIP_DOCKER_BUILD: "1"
|
||||
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
|
||||
@@ -998,6 +986,7 @@ jobs:
|
||||
shell: bash
|
||||
env:
|
||||
LANES: ${{ matrix.group.docker_lanes }}
|
||||
GROUP_LABEL: ${{ matrix.group.label }}
|
||||
INCLUDE_OPENWEBUI: ${{ inputs.include_openwebui }}
|
||||
INCLUDE_RELEASE_PATH_SUITES: ${{ inputs.include_release_path_suites }}
|
||||
run: |
|
||||
@@ -1017,7 +1006,7 @@ jobs:
|
||||
plan_path=".artifacts/docker-tests/targeted-plan.json"
|
||||
node .release-harness/scripts/test-docker-all.mjs --plan-json > "$plan_path"
|
||||
node .release-harness/scripts/docker-e2e.mjs github-outputs "$plan_path" >> "$GITHUB_OUTPUT"
|
||||
suffix="$(printf '%s' "$LANES" | tr ',[:space:]' '-' | tr -cd 'A-Za-z0-9._-' | sed -E 's/-+/-/g; s/^-//; s/-$//')"
|
||||
suffix="$(printf '%s' "${GROUP_LABEL:-$LANES}" | tr ',[:space:]' '-' | tr -cd 'A-Za-z0-9._-' | sed -E 's/-+/-/g; s/^-//; s/-$//')"
|
||||
echo "artifact_suffix=${suffix:-targeted}" >> "$GITHUB_OUTPUT"
|
||||
echo "plan_json=$plan_path" >> "$GITHUB_OUTPUT"
|
||||
|
||||
|
||||
@@ -559,7 +559,7 @@ jobs:
|
||||
package_sha256: ${{ needs.prepare_release_package.outputs.package_sha256 }}
|
||||
suite_profile: custom
|
||||
docker_lanes: doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor plugins-offline plugin-update
|
||||
published_upgrade_survivor_baselines: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'all-since-2026.4.23' || '' }}
|
||||
published_upgrade_survivor_baselines: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'last-stable-4 2026.4.23 2026.5.2 2026.4.15' || '' }}
|
||||
published_upgrade_survivor_scenarios: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'reported-issues' || '' }}
|
||||
telegram_mode: mock-openai
|
||||
telegram_scenarios: telegram-help-command,telegram-commands-command,telegram-tools-compact-command,telegram-whoami-command,telegram-context-command,telegram-current-session-status-tool,telegram-mention-gating
|
||||
|
||||
6
.github/workflows/package-acceptance.yml
vendored
6
.github/workflows/package-acceptance.yml
vendored
@@ -70,7 +70,7 @@ on:
|
||||
default: openclaw@latest
|
||||
type: string
|
||||
published_upgrade_survivor_baselines:
|
||||
description: Optional baseline list for published-upgrade-survivor/update-migration; use all-since-2026.4.23, release-history, or exact versions
|
||||
description: Optional baseline list for published-upgrade-survivor/update-migration; use last-stable-4, all-since-2026.4.23, release-history, or exact versions
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
@@ -150,7 +150,7 @@ on:
|
||||
default: openclaw@latest
|
||||
type: string
|
||||
published_upgrade_survivor_baselines:
|
||||
description: Optional baseline list for published-upgrade-survivor/update-migration; use all-since-2026.4.23, release-history, or exact versions
|
||||
description: Optional baseline list for published-upgrade-survivor/update-migration; use last-stable-4, all-since-2026.4.23, release-history, or exact versions
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
@@ -442,7 +442,7 @@ jobs:
|
||||
fi
|
||||
releases_json=""
|
||||
npm_versions_json=""
|
||||
if [[ "$REQUESTED_BASELINES" == *"release-history"* || "$REQUESTED_BASELINES" == *"all-since-"* ]]; then
|
||||
if [[ "$REQUESTED_BASELINES" == *"release-history"* || "$REQUESTED_BASELINES" == *"all-since-"* || "$REQUESTED_BASELINES" == *"last-stable-"* ]]; then
|
||||
releases_json=".artifacts/package-candidate-input/openclaw-releases.json"
|
||||
npm_versions_json=".artifacts/package-candidate-input/openclaw-npm-versions.json"
|
||||
mkdir -p "$(dirname "$releases_json")"
|
||||
|
||||
@@ -265,7 +265,7 @@ For the dedicated update and plugin testing policy, including local commands,
|
||||
Docker lanes, Package Acceptance inputs, release defaults, and failure triage,
|
||||
see [Testing updates and plugins](/help/testing-updates-plugins).
|
||||
|
||||
Release checks call Package Acceptance with `source=artifact`, the prepared release package artifact, `suite_profile=custom`, `docker_lanes='doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor plugins-offline plugin-update'`, and `telegram_mode=mock-openai`. This keeps package migration, update, stale-plugin-dependency cleanup, configured-plugin install repair, offline plugin, plugin-update, and Telegram proof on the same resolved package tarball. Set `package_acceptance_package_spec` on Full Release Validation or OpenClaw Release Checks to run that same matrix against a shipped npm package instead of the SHA-built artifact. Cross-OS release checks still cover OS-specific onboarding, installer, and platform behavior; package/update product validation should start with Package Acceptance. The `published-upgrade-survivor` Docker lane validates one published package baseline per run in the blocking release path. In Package Acceptance, the resolved `package-under-test` tarball is always the candidate and `published_upgrade_survivor_baseline` selects the fallback published baseline, defaulting to `openclaw@latest`; failed-lane rerun commands preserve that baseline. Full Release Validation with `run_release_soak=true` or `release_profile=full` sets `published_upgrade_survivor_baselines=all-since-2026.4.23` and `published_upgrade_survivor_scenarios=reported-issues` to expand across every stable npm release from `2026.4.23` through `latest` and issue-shaped fixtures for Feishu config, preserved bootstrap/persona files, configured OpenClaw plugin installs, tilde log paths, and stale legacy plugin dependency roots. The separate `Update Migration` workflow uses the `update-migration` Docker lane with `all-since-2026.4.23` and `plugin-deps-cleanup` when the question is exhaustive published update cleanup, not normal Full Release CI breadth. Local aggregate runs can pass exact package specs with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS`, keep a single lane with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC` such as `openclaw@2026.4.15`, or set `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS` for the scenario matrix. The published lane configures the baseline with a baked `openclaw config set` command recipe, records recipe steps in `summary.json`, and probes `/healthz`, `/readyz`, plus RPC status after Gateway start. The Windows packaged and installer fresh lanes also verify that an installed package can import a browser-control override from a raw absolute Windows path. The OpenAI cross-OS agent-turn smoke defaults to `OPENCLAW_CROSS_OS_OPENAI_MODEL` when set, otherwise `openai/gpt-5.4`, so the install and gateway proof stays on a GPT-5 test model while avoiding GPT-4.x defaults.
|
||||
Release checks call Package Acceptance with `source=artifact`, the prepared release package artifact, `suite_profile=custom`, `docker_lanes='doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor plugins-offline plugin-update'`, and `telegram_mode=mock-openai`. This keeps package migration, update, stale-plugin-dependency cleanup, configured-plugin install repair, offline plugin, plugin-update, and Telegram proof on the same resolved package tarball. Set `package_acceptance_package_spec` on Full Release Validation or OpenClaw Release Checks to run that same matrix against a shipped npm package instead of the SHA-built artifact. Cross-OS release checks still cover OS-specific onboarding, installer, and platform behavior; package/update product validation should start with Package Acceptance. The `published-upgrade-survivor` Docker lane validates one published package baseline per run in the blocking release path. In Package Acceptance, the resolved `package-under-test` tarball is always the candidate and `published_upgrade_survivor_baseline` selects the fallback published baseline, defaulting to `openclaw@latest`; failed-lane rerun commands preserve that baseline. Full Release Validation with `run_release_soak=true` or `release_profile=full` sets `published_upgrade_survivor_baselines='last-stable-4 2026.4.23 2026.5.2 2026.4.15'` and `published_upgrade_survivor_scenarios=reported-issues` to expand across the four latest stable npm releases plus pinned plugin-compatibility boundary releases and issue-shaped fixtures for Feishu config, preserved bootstrap/persona files, configured OpenClaw plugin installs, tilde log paths, and stale legacy plugin dependency roots. Multi-baseline published-upgrade survivor selections are sharded by baseline into separate targeted Docker runner jobs. The separate `Update Migration` workflow uses the `update-migration` Docker lane with `all-since-2026.4.23` and `plugin-deps-cleanup` when the question is exhaustive published update cleanup, not normal Full Release CI breadth. Local aggregate runs can pass exact package specs with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS`, keep a single lane with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC` such as `openclaw@2026.4.15`, or set `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS` for the scenario matrix. The published lane configures the baseline with a baked `openclaw config set` command recipe, records recipe steps in `summary.json`, and probes `/healthz`, `/readyz`, plus RPC status after Gateway start. The Windows packaged and installer fresh lanes also verify that an installed package can import a browser-control override from a raw absolute Windows path. The OpenAI cross-OS agent-turn smoke defaults to `OPENCLAW_CROSS_OS_OPENAI_MODEL` when set, otherwise `openai/gpt-5.4`, so the install and gateway proof stays on a GPT-5 test model while avoiding GPT-4.x defaults.
|
||||
|
||||
### Legacy compatibility windows
|
||||
|
||||
|
||||
@@ -170,24 +170,35 @@ Release checks call Package Acceptance with the package/update/plugin set:
|
||||
doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor plugins-offline plugin-update
|
||||
```
|
||||
|
||||
They also pass:
|
||||
When release soak is enabled, they also pass:
|
||||
|
||||
```text
|
||||
published_upgrade_survivor_baselines=all-since-2026.4.23
|
||||
published_upgrade_survivor_baselines=last-stable-4 2026.4.23 2026.5.2 2026.4.15
|
||||
published_upgrade_survivor_scenarios=reported-issues
|
||||
telegram_mode=mock-openai
|
||||
```
|
||||
|
||||
This keeps package migration, update channel switching, stale plugin dependency
|
||||
cleanup, offline plugin coverage, plugin update behavior, and Telegram package
|
||||
QA on the same resolved artifact.
|
||||
QA on the same resolved artifact without making the default release package gate
|
||||
walk every published release.
|
||||
|
||||
`all-since-2026.4.23` is the Full Release CI upgrade sample: every stable npm-published release from `2026.4.23` through `latest`. For exhaustive published
|
||||
`last-stable-4` resolves to the four latest stable npm-published OpenClaw
|
||||
releases. Release package acceptance pins `2026.4.23` as the first plugin-update
|
||||
compatibility boundary, `2026.5.2` as a plugin-architecture churn boundary, and
|
||||
`2026.4.15` as an older 2026.4.1x published-update baseline; the resolver
|
||||
dedupes pins that are already in the latest four. For exhaustive published
|
||||
update migration coverage, use `all-since-2026.4.23` in the separate Update
|
||||
Migration workflow instead of Full Release CI. `release-history` remains
|
||||
available for manual wider sampling when you also want the legacy pre-date
|
||||
anchor.
|
||||
|
||||
When multiple published-upgrade survivor baselines are selected, the reusable
|
||||
Docker workflow shards each baseline into its own targeted runner job. Each
|
||||
baseline shard still runs the selected scenario set, but logs and artifacts stay
|
||||
per-baseline and wall time is bounded by the slowest shard instead of one large
|
||||
serial job.
|
||||
|
||||
Run a package profile manually when validating a candidate before release:
|
||||
|
||||
```bash
|
||||
@@ -197,7 +208,7 @@ gh workflow run package-acceptance.yml \
|
||||
-f source=npm \
|
||||
-f package_spec=openclaw@beta \
|
||||
-f suite_profile=package \
|
||||
-f published_upgrade_survivor_baselines=all-since-2026.4.23 \
|
||||
-f published_upgrade_survivor_baselines="last-stable-4 2026.4.23 2026.5.2 2026.4.15" \
|
||||
-f published_upgrade_survivor_scenarios=reported-issues \
|
||||
-f telegram_mode=mock-openai
|
||||
```
|
||||
|
||||
@@ -643,7 +643,7 @@ The live-model Docker runners also bind-mount only the needed CLI auth homes (or
|
||||
- Npm tarball onboarding/channel/agent smoke: `pnpm test:docker:npm-onboard-channel-agent` installs the packed OpenClaw tarball globally in Docker, configures OpenAI via env-ref onboarding plus Telegram by default, runs doctor, and runs one mocked OpenAI agent turn. Reuse a prebuilt tarball with `OPENCLAW_CURRENT_PACKAGE_TGZ=/path/to/openclaw-*.tgz`, skip the host rebuild with `OPENCLAW_NPM_ONBOARD_HOST_BUILD=0`, or switch channel with `OPENCLAW_NPM_ONBOARD_CHANNEL=discord` or `OPENCLAW_NPM_ONBOARD_CHANNEL=slack`.
|
||||
- Update channel switch smoke: `pnpm test:docker:update-channel-switch` installs the packed OpenClaw tarball globally in Docker, switches from package `stable` to git `dev`, verifies the persisted channel and plugin post-update work, then switches back to package `stable` and checks update status.
|
||||
- Upgrade survivor smoke: `pnpm test:docker:upgrade-survivor` installs the packed OpenClaw tarball over a dirty old-user fixture with agents, channel config, plugin allowlists, stale plugin dependency state, and existing workspace/session files. It runs package update plus non-interactive doctor without live provider or channel keys, then starts a loopback Gateway and checks config/state preservation plus startup/status budgets.
|
||||
- Published upgrade survivor smoke: `pnpm test:docker:published-upgrade-survivor` installs `openclaw@latest` by default, seeds realistic existing-user files, configures that baseline with a baked command recipe, validates the resulting config, updates that published install to the candidate tarball, runs non-interactive doctor, writes `.artifacts/upgrade-survivor/summary.json`, then starts a loopback Gateway and checks configured intents, state preservation, startup, `/healthz`, `/readyz`, and RPC status budgets. Override one baseline with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC`, ask the aggregate scheduler to expand exact baselines with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS` such as `all-since-2026.4.23`, and expand issue-shaped fixtures with `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS` such as `reported-issues`; the reported-issues set includes `configured-plugin-installs` for automatic external OpenClaw plugin install repair. Package Acceptance exposes those as `published_upgrade_survivor_baseline`, `published_upgrade_survivor_baselines`, and `published_upgrade_survivor_scenarios`; Full Release Validation uses the default latest baseline in the blocking path and expands to all-since/reported-issues only for `run_release_soak=true` or `release_profile=full`.
|
||||
- Published upgrade survivor smoke: `pnpm test:docker:published-upgrade-survivor` installs `openclaw@latest` by default, seeds realistic existing-user files, configures that baseline with a baked command recipe, validates the resulting config, updates that published install to the candidate tarball, runs non-interactive doctor, writes `.artifacts/upgrade-survivor/summary.json`, then starts a loopback Gateway and checks configured intents, state preservation, startup, `/healthz`, `/readyz`, and RPC status budgets. Override one baseline with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC`, ask the aggregate scheduler to expand exact baselines with `OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS` such as `last-stable-4 2026.4.23 2026.5.2 2026.4.15` or `all-since-2026.4.23`, and expand issue-shaped fixtures with `OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS` such as `reported-issues`; the reported-issues set includes `configured-plugin-installs` for automatic external OpenClaw plugin install repair. Package Acceptance exposes those as `published_upgrade_survivor_baseline`, `published_upgrade_survivor_baselines`, and `published_upgrade_survivor_scenarios`; Full Release Validation uses the default latest baseline in the blocking path and expands the release-soak package gate to `last-stable-4 2026.4.23 2026.5.2 2026.4.15` plus `reported-issues`.
|
||||
- Session runtime context smoke: `pnpm test:docker:session-runtime-context` verifies hidden runtime context transcript persistence plus doctor repair of affected duplicated prompt-rewrite branches.
|
||||
- Bun global install smoke: `bash scripts/e2e/bun-global-install-smoke.sh` packs the current tree, installs it with `bun install -g` in an isolated home, and verifies `openclaw infer image providers --json` returns bundled image providers instead of hanging. Reuse a prebuilt tarball with `OPENCLAW_BUN_GLOBAL_SMOKE_PACKAGE_TGZ=/path/to/openclaw-*.tgz`, skip the host build with `OPENCLAW_BUN_GLOBAL_SMOKE_HOST_BUILD=0`, or copy `dist/` from a built Docker image with `OPENCLAW_BUN_GLOBAL_SMOKE_DIST_IMAGE=openclaw-dockerfile-smoke:local`.
|
||||
- Installer Docker smoke: `bash scripts/test-install-sh-docker.sh` shares one npm cache across its root, update, and direct-npm containers. Update smoke defaults to npm `latest` as the stable baseline before upgrading to the candidate tarball. Override with `OPENCLAW_INSTALL_SMOKE_UPDATE_BASELINE=2026.4.22` locally, or with the Install Smoke workflow's `update_baseline_version` input on GitHub. Non-root installer checks keep an isolated npm cache so root-owned cache entries do not mask user-local install behavior. Set `OPENCLAW_INSTALL_SMOKE_NPM_CACHE_DIR=/path/to/cache` to reuse the root/update/direct-npm cache across local reruns.
|
||||
|
||||
@@ -322,7 +322,10 @@ Use `release_profile` to select live/provider breadth:
|
||||
|
||||
Use `run_release_soak=true` with `stable` when the release-blocking lanes are
|
||||
green and you want the exhaustive live/E2E, Docker release-path, and
|
||||
all-since-2026.4.23 upgrade-survivor sweep before promotion. `full` implies
|
||||
bounded published upgrade-survivor sweep before promotion. That sweep covers
|
||||
the latest four stable packages plus pinned `2026.4.23` and `2026.5.2`
|
||||
baselines plus older `2026.4.15` coverage, with duplicate baselines removed and
|
||||
each baseline sharded into its own Docker runner job. `full` implies
|
||||
`run_release_soak=true`.
|
||||
|
||||
`OpenClaw Release Checks` uses the trusted workflow ref to resolve the target
|
||||
|
||||
@@ -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 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: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 `last-stable-4 2026.4.23 2026.5.2 2026.4.15` or `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.
|
||||
|
||||
|
||||
97
scripts/plan-targeted-docker-lane-groups.mjs
Normal file
97
scripts/plan-targeted-docker-lane-groups.mjs
Normal file
@@ -0,0 +1,97 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const BASELINE_SHARDED_LANES = new Set(["published-upgrade-survivor", "update-migration"]);
|
||||
|
||||
function splitTokens(raw) {
|
||||
return [
|
||||
...new Set(
|
||||
String(raw ?? "")
|
||||
.split(/[,\s]+/u)
|
||||
.map((token) => token.trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
function parsePositiveInt(raw, fallback, label) {
|
||||
const parsed = Number.parseInt(String(raw ?? ""), 10);
|
||||
if (!Number.isFinite(parsed)) {
|
||||
return fallback;
|
||||
}
|
||||
if (parsed < 1) {
|
||||
throw new Error(`${label} must be a positive integer. Got: ${JSON.stringify(raw)}`);
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function sanitizeLabel(value) {
|
||||
return (
|
||||
String(value)
|
||||
.replace(/^openclaw@/u, "")
|
||||
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "") || "targeted"
|
||||
);
|
||||
}
|
||||
|
||||
export function planTargetedDockerLaneGroups({
|
||||
groupSize = 1,
|
||||
lanes,
|
||||
upgradeSurvivorBaselines = "",
|
||||
} = {}) {
|
||||
const selectedLanes = splitTokens(lanes);
|
||||
if (selectedLanes.length === 0) {
|
||||
throw new Error("docker_lanes is required when planning targeted Docker lane groups.");
|
||||
}
|
||||
|
||||
const parsedGroupSize = parsePositiveInt(groupSize, 1, "groupSize");
|
||||
const baselineSpecs = splitTokens(upgradeSurvivorBaselines);
|
||||
const groups = [];
|
||||
let pendingLanes = [];
|
||||
|
||||
const flushPending = () => {
|
||||
if (pendingLanes.length === 0) {
|
||||
return;
|
||||
}
|
||||
const first = sanitizeLabel(pendingLanes[0]);
|
||||
const last = sanitizeLabel(pendingLanes[pendingLanes.length - 1]);
|
||||
const label = pendingLanes.length === 1 ? first : `${first}--${last}`;
|
||||
groups.push({ docker_lanes: pendingLanes.join(" "), label });
|
||||
pendingLanes = [];
|
||||
};
|
||||
|
||||
for (const lane of selectedLanes) {
|
||||
if (BASELINE_SHARDED_LANES.has(lane) && baselineSpecs.length > 1) {
|
||||
flushPending();
|
||||
for (const baselineSpec of baselineSpecs) {
|
||||
groups.push({
|
||||
docker_lanes: lane,
|
||||
label: `${sanitizeLabel(lane)}-${sanitizeLabel(baselineSpec)}`,
|
||||
published_upgrade_survivor_baselines: baselineSpec,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
pendingLanes.push(lane);
|
||||
if (pendingLanes.length >= parsedGroupSize) {
|
||||
flushPending();
|
||||
}
|
||||
}
|
||||
|
||||
flushPending();
|
||||
return groups;
|
||||
}
|
||||
|
||||
const isMain = process.argv[1] ? fileURLToPath(import.meta.url) === process.argv[1] : false;
|
||||
|
||||
if (isMain) {
|
||||
process.stdout.write(
|
||||
JSON.stringify(
|
||||
planTargetedDockerLaneGroups({
|
||||
groupSize: process.env.GROUP_SIZE,
|
||||
lanes: process.env.LANES,
|
||||
upgradeSurvivorBaselines: process.env.OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -128,6 +128,19 @@ export function resolveReleaseHistory(args) {
|
||||
return dedupeSpecs(versions);
|
||||
}
|
||||
|
||||
export function resolveLastStable(args, count) {
|
||||
const releasesJson = args.get("releases-json");
|
||||
if (!releasesJson) {
|
||||
throw new Error("--releases-json is required when requested baselines include last-stable-*");
|
||||
}
|
||||
if (!Number.isInteger(count) || count < 1) {
|
||||
throw new Error(`invalid last-stable baseline count: ${count}`);
|
||||
}
|
||||
const publishedVersions = readPublishedVersions(args.get("npm-versions-json"));
|
||||
const releases = readStableReleases(releasesJson, publishedVersions);
|
||||
return dedupeSpecs(releases.slice(0, count).map((release) => release.version));
|
||||
}
|
||||
|
||||
export function resolveAllSince(args, minimumVersion) {
|
||||
const releasesJson = args.get("releases-json");
|
||||
if (!releasesJson) {
|
||||
@@ -149,11 +162,13 @@ export function resolveBaselines(args) {
|
||||
if (requestedTokens.length === 0) {
|
||||
return dedupeSpecs([fallback]);
|
||||
}
|
||||
const exactTokens = [];
|
||||
const resolved = [];
|
||||
for (const token of requestedTokens) {
|
||||
if (token === "release-history") {
|
||||
resolved.push(...resolveReleaseHistory(args));
|
||||
} else if (token.startsWith("last-stable-")) {
|
||||
const count = Number.parseInt(token.slice("last-stable-".length), 10);
|
||||
resolved.push(...resolveLastStable(args, count));
|
||||
} else if (token.startsWith("all-since-")) {
|
||||
const minimumVersion = token.slice("all-since-".length);
|
||||
if (!parseStableVersion(minimumVersion)) {
|
||||
@@ -161,10 +176,10 @@ export function resolveBaselines(args) {
|
||||
}
|
||||
resolved.push(...resolveAllSince(args, minimumVersion));
|
||||
} else {
|
||||
exactTokens.push(token);
|
||||
resolved.push(token);
|
||||
}
|
||||
}
|
||||
return dedupeSpecs([...exactTokens, ...resolved]);
|
||||
return dedupeSpecs(resolved);
|
||||
}
|
||||
|
||||
const isMain = process.argv[1] ? fileURLToPath(import.meta.url) === process.argv[1] : false;
|
||||
|
||||
@@ -92,12 +92,14 @@ describe("package acceptance workflow", () => {
|
||||
expect(workflow).toContain("suite_profile:");
|
||||
expect(workflow).toContain("published_upgrade_survivor_baseline:");
|
||||
expect(workflow).toContain("published_upgrade_survivor_baselines:");
|
||||
expect(workflow).toContain("last-stable-4");
|
||||
expect(workflow).toContain("all-since-2026.4.23");
|
||||
expect(workflow).toContain("published_upgrade_survivor_scenarios:");
|
||||
expect(workflow).toContain("scripts/resolve-upgrade-survivor-baselines.mjs");
|
||||
expect(workflow).toContain("--history-count 6");
|
||||
expect(workflow).toContain("--include-version 2026.4.23");
|
||||
expect(workflow).toContain("--pre-date 2026-03-15T00:00:00Z");
|
||||
expect(workflow).toContain('"last-stable-"');
|
||||
expect(workflow).toContain('"all-since-"');
|
||||
expect(workflow).toContain("npm-onboard-channel-agent gateway-network config-reload");
|
||||
expect(workflow).toContain("npm-onboard-channel-agent doctor-switch");
|
||||
@@ -199,7 +201,7 @@ describe("package artifact reuse", () => {
|
||||
"OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPEC: ${{ inputs.published_upgrade_survivor_baseline }}",
|
||||
);
|
||||
expect(workflow).toContain(
|
||||
"OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ inputs.published_upgrade_survivor_baselines }}",
|
||||
"OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ matrix.group.published_upgrade_survivor_baselines || inputs.published_upgrade_survivor_baselines }}",
|
||||
);
|
||||
expect(workflow).toContain(
|
||||
"OPENCLAW_UPGRADE_SURVIVOR_SCENARIOS: ${{ inputs.published_upgrade_survivor_scenarios }}",
|
||||
@@ -229,8 +231,13 @@ describe("package artifact reuse", () => {
|
||||
});
|
||||
expect(workflow).toContain("plan_docker_lane_groups:");
|
||||
expect(workflow).toContain("targeted_docker_lane_group_size:");
|
||||
expect(workflow).toContain("scripts/plan-targeted-docker-lane-groups.mjs");
|
||||
expect(workflow).toContain(
|
||||
"OPENCLAW_UPGRADE_SURVIVOR_BASELINE_SPECS: ${{ inputs.published_upgrade_survivor_baselines }}",
|
||||
);
|
||||
expect(workflow).toContain("Docker E2E targeted lanes (${{ matrix.group.label }})");
|
||||
expect(workflow).toContain("LANES: ${{ matrix.group.docker_lanes }}");
|
||||
expect(workflow).toContain("GROUP_LABEL: ${{ matrix.group.label }}");
|
||||
expect(workflow).toContain("DOCKER_E2E_LANES: ${{ matrix.group.docker_lanes }}");
|
||||
expect(workflow).toContain("name: docker-e2e-${{ steps.plan.outputs.artifact_suffix }}");
|
||||
expect(scheduler).toContain(
|
||||
@@ -530,7 +537,7 @@ describe("package artifact reuse", () => {
|
||||
"docker_lanes: doctor-switch update-channel-switch upgrade-survivor published-upgrade-survivor plugins-offline plugin-update",
|
||||
);
|
||||
expect(workflow).toContain(
|
||||
"published_upgrade_survivor_baselines: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'all-since-2026.4.23' || '' }}",
|
||||
"published_upgrade_survivor_baselines: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'last-stable-4 2026.4.23 2026.5.2 2026.4.15' || '' }}",
|
||||
);
|
||||
expect(workflow).toContain(
|
||||
"published_upgrade_survivor_scenarios: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'reported-issues' || '' }}",
|
||||
|
||||
68
test/scripts/targeted-docker-lane-groups.test.ts
Normal file
68
test/scripts/targeted-docker-lane-groups.test.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { planTargetedDockerLaneGroups } from "../../scripts/plan-targeted-docker-lane-groups.mjs";
|
||||
|
||||
describe("scripts/plan-targeted-docker-lane-groups", () => {
|
||||
it("keeps normal targeted lanes grouped by the configured group size", () => {
|
||||
expect(
|
||||
planTargetedDockerLaneGroups({
|
||||
groupSize: 2,
|
||||
lanes: "doctor-switch update-channel-switch plugin-update",
|
||||
}),
|
||||
).toEqual([
|
||||
{
|
||||
docker_lanes: "doctor-switch update-channel-switch",
|
||||
label: "doctor-switch--update-channel-switch",
|
||||
},
|
||||
{ docker_lanes: "plugin-update", label: "plugin-update" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("shards published upgrade survivor by baseline while preserving surrounding lanes", () => {
|
||||
expect(
|
||||
planTargetedDockerLaneGroups({
|
||||
groupSize: 2,
|
||||
lanes:
|
||||
"doctor-switch update-channel-switch published-upgrade-survivor plugins-offline plugin-update",
|
||||
upgradeSurvivorBaselines:
|
||||
"openclaw@2026.5.3-1 openclaw@2026.5.3 openclaw@2026.5.2 openclaw@2026.4.23",
|
||||
}),
|
||||
).toEqual([
|
||||
{
|
||||
docker_lanes: "doctor-switch update-channel-switch",
|
||||
label: "doctor-switch--update-channel-switch",
|
||||
},
|
||||
{
|
||||
docker_lanes: "published-upgrade-survivor",
|
||||
label: "published-upgrade-survivor-2026.5.3-1",
|
||||
published_upgrade_survivor_baselines: "openclaw@2026.5.3-1",
|
||||
},
|
||||
{
|
||||
docker_lanes: "published-upgrade-survivor",
|
||||
label: "published-upgrade-survivor-2026.5.3",
|
||||
published_upgrade_survivor_baselines: "openclaw@2026.5.3",
|
||||
},
|
||||
{
|
||||
docker_lanes: "published-upgrade-survivor",
|
||||
label: "published-upgrade-survivor-2026.5.2",
|
||||
published_upgrade_survivor_baselines: "openclaw@2026.5.2",
|
||||
},
|
||||
{
|
||||
docker_lanes: "published-upgrade-survivor",
|
||||
label: "published-upgrade-survivor-2026.4.23",
|
||||
published_upgrade_survivor_baselines: "openclaw@2026.4.23",
|
||||
},
|
||||
{ docker_lanes: "plugins-offline plugin-update", label: "plugins-offline--plugin-update" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("leaves a single baseline on the normal logical lane", () => {
|
||||
expect(
|
||||
planTargetedDockerLaneGroups({
|
||||
lanes: "published-upgrade-survivor",
|
||||
upgradeSurvivorBaselines: "openclaw@2026.5.2",
|
||||
}),
|
||||
).toEqual([
|
||||
{ docker_lanes: "published-upgrade-survivor", label: "published-upgrade-survivor" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -115,6 +115,49 @@ describe("scripts/resolve-upgrade-survivor-baselines", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("resolves last-stable baselines to the latest stable published package versions", () => {
|
||||
const releases = (
|
||||
[
|
||||
["v2026.5.4-beta.1", "2026-05-05T00:00:00Z", true],
|
||||
["v2026.5.3-1", "2026-05-04T00:00:00Z"],
|
||||
["v2026.5.3", "2026-05-03T00:00:00Z"],
|
||||
["v2026.5.2", "2026-05-02T00:00:00Z"],
|
||||
["v2026.4.29", "2026-04-30T00:00:00Z"],
|
||||
["v2026.4.27", "2026-04-28T00:00:00Z"],
|
||||
["v2026.4.15", "2026-04-16T00:00:00Z"],
|
||||
] as const
|
||||
).map(([tagName, publishedAt, isPrerelease = false]) => ({
|
||||
isPrerelease,
|
||||
publishedAt,
|
||||
tagName,
|
||||
}));
|
||||
|
||||
withReleaseFixture(releases, (releasesFile) => {
|
||||
withJsonFixture(
|
||||
"versions.json",
|
||||
["2026.5.3-1", "2026.5.3", "2026.5.2", "2026.4.29", "2026.4.27", "2026.4.15"],
|
||||
(versionsFile) => {
|
||||
expect(
|
||||
resolveBaselines(
|
||||
new Map([
|
||||
["requested", "last-stable-4 2026.4.23 2026.5.2 2026.4.15"],
|
||||
["releases-json", releasesFile],
|
||||
["npm-versions-json", versionsFile],
|
||||
]),
|
||||
),
|
||||
).toEqual([
|
||||
"openclaw@2026.5.3-1",
|
||||
"openclaw@2026.5.3",
|
||||
"openclaw@2026.5.2",
|
||||
"openclaw@2026.4.29",
|
||||
"openclaw@2026.4.23",
|
||||
"openclaw@2026.4.15",
|
||||
]);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("maps release-history anchors to npm-published package versions when GitHub tags have republish suffixes", () => {
|
||||
const releases = (
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user