mirror of
https://github.com/nearai/ironclaw.git
synced 2026-09-02 23:56:24 +08:00
Bumps the actions group with 5 updates in the / directory: | Package | From | To | | --- | --- | --- | | [anthropics/claude-code-action](https://github.com/anthropics/claude-code-action) | `1.0.183` | `1.0.208` | | [actions/setup-node](https://github.com/actions/setup-node) | `4.0.2` | `7.0.0` | | [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) | `e18b497796c12c097a38f9edb9d0641fb99eee32` | `f0d9c3887740aee45f6153b24b3a6b815192ec16` | | [docker/login-action](https://github.com/docker/login-action) | `4.5.2` | `4.6.0` | | [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `4.2.0` | `4.3.0` | Updates `anthropics/claude-code-action` from 1.0.183 to 1.0.208 - [Release notes](https://github.com/anthropics/claude-code-action/releases) - [Commits](be7b93b190...e8c2d7c16c) Updates `actions/setup-node` from 4.0.2 to 7.0.0 - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v4.0.2...v7) Updates `Swatinem/rust-cache` from e18b497796c12c097a38f9edb9d0641fb99eee32 to f0d9c3887740aee45f6153b24b3a6b815192ec16 - [Release notes](https://github.com/swatinem/rust-cache/releases) - [Changelog](https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md) - [Commits](e18b497796...f0d9c38877) Updates `docker/login-action` from 4.5.2 to 4.6.0 - [Release notes](https://github.com/docker/login-action/releases) - [Commits](371161bbe7...dbcb813823) Updates `docker/setup-buildx-action` from 4.2.0 to 4.3.0 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](bb05f3f551...37fe631027) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: 7.0.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: anthropics/claude-code-action dependency-version: 1.0.198 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions - dependency-name: docker/login-action dependency-version: 4.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions - dependency-name: docker/setup-buildx-action dependency-version: 4.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions - dependency-name: Swatinem/rust-cache dependency-version: f0d9c3887740aee45f6153b24b3a6b815192ec16 dependency-type: direct:production dependency-group: actions ... Signed-off-by: dependabot[bot] <support@github.com>
695 lines
28 KiB
YAML
695 lines
28 KiB
YAML
name: Reborn E2E
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
ref:
|
|
description: Commit SHA or ref to test
|
|
required: false
|
|
type: string
|
|
reversed_journey_order:
|
|
description: >-
|
|
Also replay the mutating provider journeys back to front in one
|
|
shared provider world, without resetting between cases. Off for pull
|
|
requests (it repeats the slowest lane); the nightly deep run turns it
|
|
on.
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
alone_journey_replay:
|
|
description: >-
|
|
Also replay each mutating provider journey in its own pytest
|
|
process, proving it passes with no other journey before it. Off for
|
|
pull requests (one server start per journey); the nightly deep run
|
|
turns it on.
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
workflow_dispatch:
|
|
# pull_request deliberately has no `paths:` filter: the "Reborn E2E"
|
|
# roll-up must report on every PR so it can be a required status check (a
|
|
# required check that never reports blocks queueing). The `changes` job
|
|
# below resolves scope instead and skips the heavy jobs on out-of-scope
|
|
# PRs.
|
|
pull_request:
|
|
branches:
|
|
# Release branches get the same gate as main: a release-blocker fix
|
|
# opened against a frozen candidate must clear the E2E lanes before it
|
|
# can produce a new rc. `release-fix-*` is the 1.0.0-era name;
|
|
# `release/*` is the weekly cut named in
|
|
# docs/internal/weekly-release-strategy.md.
|
|
- main
|
|
- "release-fix-*"
|
|
- "release/*"
|
|
# The merge queue is the production gate: the deterministic Reborn contract
|
|
# and smoke coverage must pass on the merged state before it reaches main.
|
|
# merge_group does not support `paths:` filters, so scope is resolved by the
|
|
# `changes` job below instead.
|
|
merge_group:
|
|
branches:
|
|
- main
|
|
types:
|
|
- checks_requested
|
|
push:
|
|
branches:
|
|
- main
|
|
paths:
|
|
# `crates/**`, not `crates/ironclaw_*/**`: a filter keyed to the flat
|
|
# crate layout silently stops matching when crates move into family
|
|
# directories (`crates/<family>/ironclaw_*`, PROPOSAL §5) — every E2E job
|
|
# skipped, roll-up green, nothing run. Depth-independent by construction.
|
|
# See docs/internal/reborn/target-architecture/CHECKLIST.md WS10.
|
|
- "crates/**"
|
|
- "docs/internal/reborn/**"
|
|
- "scripts/reborn-e2e-rust.sh"
|
|
- "scripts/ci/*hermetic*"
|
|
- "tests/e2e/**"
|
|
- "build.rs"
|
|
- "Cargo.toml"
|
|
- "Cargo.lock"
|
|
- ".github/workflows/reborn-e2e.yml"
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: reborn-e2e-${{ github.event_name }}-${{ github.head_ref || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
# Pin wall-clock rendering inputs so a browser/API assertion cannot pass or
|
|
# fail on the runner's locale. This suite asserts rendered timestamps and
|
|
# schedule text, which are exactly the values that shift with TZ/locale.
|
|
TZ: "UTC"
|
|
LANG: "C.UTF-8"
|
|
IRONCLAW_E2E_EMULATE_SLACK_CHANNEL_BEARER: "emulate-slack-channel-token"
|
|
|
|
jobs:
|
|
changes:
|
|
name: Detect Reborn E2E scope
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
outputs:
|
|
has_e2e_scope: ${{ steps.non_diff.outputs.has_e2e_scope || steps.diff.outputs.has_e2e_scope }}
|
|
steps:
|
|
# push is scoped by its `paths:` filter in the `on:` block, and
|
|
# workflow_call/workflow_dispatch callers want the full gate.
|
|
- id: non_diff
|
|
if: github.event_name != 'merge_group' && github.event_name != 'pull_request'
|
|
run: echo "has_e2e_scope=true" >> "$GITHUB_OUTPUT"
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
|
|
if: github.event_name == 'merge_group' || github.event_name == 'pull_request'
|
|
with:
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
- id: diff
|
|
if: github.event_name == 'merge_group' || github.event_name == 'pull_request'
|
|
env:
|
|
BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.merge_group.base_sha }}
|
|
HEAD_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
|
run: |
|
|
CHANGED_FILES="$(git diff --name-only "$BASE_SHA"..."$HEAD_SHA")"
|
|
|
|
# Keep this pattern in sync with the push `paths:` filter in the
|
|
# `on:` block above. `^crates/` is deliberately depth-independent
|
|
# for the same reason (CHECKLIST WS10); both forms are pinned by
|
|
# scripts/ci/ws12_workflow_contracts.py, which replays a nested
|
|
# crate path through this exact regex.
|
|
if printf '%s\n' "$CHANGED_FILES" | grep -Eq '^(crates/|docs/internal/reborn/|scripts/reborn-e2e-rust\.sh$|scripts/ci/[^/]*hermetic[^/]*$|tests/e2e/|build\.rs$|Cargo\.toml$|Cargo\.lock$|\.github/workflows/reborn-e2e\.yml$)'; then
|
|
echo "has_e2e_scope=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "has_e2e_scope=false" >> "$GITHUB_OUTPUT"
|
|
echo "No Reborn E2E scope in this change — E2E jobs will be skipped"
|
|
fi
|
|
|
|
rust-reborn:
|
|
name: Rust Reborn (${{ matrix.group }})
|
|
needs: changes
|
|
# Tests (Reborn) already runs these crate/root/integration contracts for
|
|
# pull requests. Keep this exhaustive grouping for merged-state and main
|
|
# validation without spending four additional PR runners on duplicates.
|
|
if: needs.changes.outputs.has_e2e_scope == 'true' && github.event_name != 'pull_request'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 35
|
|
env:
|
|
# Full-runtime debug futures exceed libtest's default thread stack in
|
|
# merged-state runs. Match the root Reborn lane's measured headroom.
|
|
RUST_MIN_STACK: "67108864"
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
group:
|
|
- architecture-boundaries
|
|
- architecture-runtime
|
|
- runtimes
|
|
- substrates
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
|
|
with:
|
|
ref: ${{ inputs.ref || github.sha }}
|
|
persist-credentials: false
|
|
|
|
- name: Install Rust
|
|
uses: ./.github/actions/setup-rust
|
|
with:
|
|
mold: true
|
|
|
|
- name: Restore Rust cache
|
|
uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2
|
|
with:
|
|
key: reborn-e2e-${{ matrix.group }}
|
|
save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
|
|
|
- name: Run deterministic Reborn Rust gate
|
|
env:
|
|
CARGO_TEST_ARGS: "-- --nocapture"
|
|
run: >-
|
|
scripts/ci/run-hermetic-deterministic-suite.sh
|
|
rust-e2e
|
|
${{ matrix.group }}
|
|
|
|
webui-v2-smoke:
|
|
name: Reborn WebUI v2 build
|
|
needs: changes
|
|
if: needs.changes.outputs.has_e2e_scope == 'true'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
outputs:
|
|
product_ref: ${{ steps.live_canary_binary.outputs.product_ref }}
|
|
env:
|
|
IRONCLAW_HERMETIC_SUITE_SKIP_PREPARE: "1"
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
|
|
with:
|
|
# Live canary validates and tests the PR head SHA, so publish the
|
|
# reusable binary for that same immutable commit. Merge-group runs
|
|
# continue to use github.sha and test the synthesized merge commit.
|
|
ref: ${{ inputs.ref || (github.event_name == 'pull_request' && github.event.pull_request.head.sha) || github.sha }}
|
|
persist-credentials: false
|
|
|
|
- name: Install Rust
|
|
uses: ./.github/actions/setup-rust
|
|
|
|
- name: Enable pnpm for setup-node cache
|
|
run: corepack enable pnpm
|
|
|
|
- name: Install Node.js for WebUI bundle build
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v4
|
|
with:
|
|
node-version: "24"
|
|
cache: "pnpm"
|
|
# actions/setup-node hashes this list to build the cache key
|
|
# (verified against the pinned commit's bundled dist/setup/index.js:
|
|
# `hashFiles` walks one globber built from every newline-separated
|
|
# pattern and only throws when the COMBINED walk finds nothing), so
|
|
# the exact line stays live for today's tree (WS7 put the crate in
|
|
# `crates/product/`) and the deeper glob picks up any later re-homing
|
|
# without either one needing to be conditional. The two must not
|
|
# overlap: `ws12_workflow_contracts.py` rejects a spare that already
|
|
# matches the real location, because a pattern matching everything is
|
|
# broad rather than depth-tolerant.
|
|
cache-dependency-path: |
|
|
crates/product/ironclaw_webui/frontend/pnpm-lock.yaml
|
|
crates/*/*/ironclaw_webui/frontend/pnpm-lock.yaml
|
|
|
|
- name: Enable pnpm
|
|
run: corepack enable pnpm
|
|
|
|
- name: Check Reborn Responses API E2E inventory
|
|
run: python3 scripts/ci/check-reborn-responses-e2e-manifest.py
|
|
|
|
- name: Install WebUI frontend dependencies
|
|
run: |
|
|
set -euo pipefail
|
|
webui_dir="$(bash scripts/ci/crate-dir.sh ironclaw_webui)"
|
|
cd "${webui_dir}/frontend"
|
|
pnpm install --frozen-lockfile
|
|
|
|
- name: Install Python
|
|
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
with:
|
|
python-version: "3.12"
|
|
cache: pip
|
|
cache-dependency-path: tests/e2e/pyproject.toml
|
|
|
|
- name: Restore Rust cache
|
|
uses: Swatinem/rust-cache@f0d9c3887740aee45f6153b24b3a6b815192ec16 # v2
|
|
with:
|
|
key: reborn-e2e-webui-v2-smoke
|
|
save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
|
|
|
- name: Setup OVH sccache
|
|
uses: ./.github/actions/setup-sccache-dist
|
|
with:
|
|
# Keep this Wasmtime-heavy binary cache-only. Distributed builders
|
|
# cannot access generated Wasmtime/Wiggle registry inputs.
|
|
cache-ssh-host: ${{ vars.SCCACHE_CACHE_SSH_HOST }}
|
|
cache-ssh-user: ${{ vars.SCCACHE_CACHE_SSH_USER }}
|
|
cache-ssh-port: ${{ vars.SCCACHE_CACHE_SSH_PORT }}
|
|
cache-ssh-private-key: ${{ secrets.SCCACHE_CACHE_SSH_PRIVATE_KEY }}
|
|
cache-ssh-known-hosts: ${{ secrets.SCCACHE_CACHE_SSH_KNOWN_HOSTS }}
|
|
redis-password: ${{ secrets.SCCACHE_REDIS_PASSWORD }}
|
|
|
|
- name: Build product and prepare evidence dependencies
|
|
run: |
|
|
set -euo pipefail
|
|
(
|
|
set -o pipefail
|
|
{
|
|
target_dir="${CARGO_TARGET_DIR:-target}"
|
|
default_binary="${RUNNER_TEMP}/ironclaw-default"
|
|
cargo build -p ironclaw --bin ironclaw
|
|
cp "${target_dir}/debug/ironclaw" "${default_binary}"
|
|
cargo build -p ironclaw --bin ironclaw \
|
|
--features test-support
|
|
mkdir -p "${target_dir}/e2e-sso/debug"
|
|
cp "${target_dir}/debug/ironclaw" \
|
|
"${target_dir}/e2e-sso/debug/ironclaw"
|
|
mv "${default_binary}" "${target_dir}/debug/ironclaw"
|
|
} 2>&1 | sed -u 's/^/[product-build] /'
|
|
) &
|
|
product_build_pid=$!
|
|
(
|
|
set -o pipefail
|
|
{
|
|
cd tests/e2e
|
|
pip install -e .
|
|
} 2>&1 | sed -u 's/^/[evidence-setup] /'
|
|
) &
|
|
evidence_setup_pid=$!
|
|
|
|
status=0
|
|
wait "${product_build_pid}" || status=1
|
|
wait "${evidence_setup_pid}" || status=1
|
|
exit "${status}"
|
|
|
|
- name: Mark OpenAI-compatible binary build
|
|
run: touch target/debug/.ironclaw-reborn-openai-compat.stamp
|
|
|
|
- name: Validate product-surface evidence contracts
|
|
run: >-
|
|
scripts/ci/run-hermetic-deterministic-suite.sh
|
|
command
|
|
pytest
|
|
tests/e2e/scenarios/test_product_surface_coverage.py
|
|
tests/e2e/scenarios/test_provider_capability_inventory.py
|
|
tests/e2e/scenarios/test_journey_coverage.py
|
|
-q
|
|
|
|
- id: product_surface_coverage
|
|
name: Generate product-surface coverage matrix
|
|
run: |
|
|
source_commit="$(git rev-parse HEAD)"
|
|
echo "source_commit=${source_commit}" >> "$GITHUB_OUTPUT"
|
|
set +e
|
|
python tests/e2e/product_surface_coverage.py \
|
|
--json artifacts/product-surface-coverage/matrix.json \
|
|
--markdown artifacts/product-surface-coverage/matrix.md
|
|
coverage_status=$?
|
|
if [[ -f artifacts/product-surface-coverage/matrix.md ]]; then
|
|
cat artifacts/product-surface-coverage/matrix.md >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|
|
exit "$coverage_status"
|
|
|
|
- name: Upload product-surface coverage matrix
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: product-surface-coverage-${{ steps.product_surface_coverage.outputs.source_commit }}
|
|
path: artifacts/product-surface-coverage/
|
|
if-no-files-found: error
|
|
retention-days: 14
|
|
|
|
- name: Package Reborn E2E binaries
|
|
id: live_canary_binary
|
|
run: |
|
|
set -euo pipefail
|
|
product_ref="$(git rev-parse HEAD)"
|
|
live_dir="artifacts/reborn-webui-v2-binary"
|
|
sso_dir="artifacts/reborn-webui-v2-sso-binary"
|
|
mkdir -p "${live_dir}" "${sso_dir}"
|
|
|
|
# Temporary compatibility member for downstream QA consumers; remove
|
|
# after every consumer invokes the canonical ironclaw binary.
|
|
cp target/debug/ironclaw target/debug/ironclaw-reborn
|
|
tar -C target/debug -cf - ironclaw ironclaw-reborn \
|
|
| gzip -1 > "${live_dir}/ironclaw-reborn.tar.gz" &
|
|
live_package_pid=$!
|
|
tar -C "${CARGO_TARGET_DIR:-target}/e2e-sso/debug" -cf - ironclaw \
|
|
| gzip -1 > "${sso_dir}/ironclaw-sso.tar.gz" &
|
|
sso_package_pid=$!
|
|
status=0
|
|
wait "${live_package_pid}" || status=1
|
|
wait "${sso_package_pid}" || status=1
|
|
if [[ "${status}" -ne 0 ]]; then
|
|
echo "Failed to package one or more Reborn E2E binaries" >&2
|
|
exit "${status}"
|
|
fi
|
|
(
|
|
cd "${live_dir}"
|
|
sha256sum ironclaw-reborn.tar.gz > ironclaw-reborn.tar.gz.sha256
|
|
)
|
|
jq -n \
|
|
--arg product_ref "${product_ref}" \
|
|
--argjson features '[]' \
|
|
'{format_version: 1, product_ref: $product_ref, features: $features}' \
|
|
> "${live_dir}/manifest.json"
|
|
echo "product_ref=${product_ref}" >> "${GITHUB_OUTPUT}"
|
|
|
|
- name: Upload Reborn SSO test binary
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: reborn-webui-v2-sso-binary-${{ steps.live_canary_binary.outputs.product_ref }}
|
|
path: artifacts/reborn-webui-v2-sso-binary/
|
|
if-no-files-found: error
|
|
compression-level: 0
|
|
retention-days: 1
|
|
|
|
- name: Upload live-canary binary
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: reborn-webui-v2-binary-${{ steps.live_canary_binary.outputs.product_ref }}
|
|
path: artifacts/reborn-webui-v2-binary/
|
|
if-no-files-found: error
|
|
compression-level: 0
|
|
retention-days: 7
|
|
|
|
webui-v2-test-lanes:
|
|
name: Reborn WebUI v2 E2E (${{ matrix.lane }})
|
|
needs:
|
|
- changes
|
|
- webui-v2-smoke
|
|
if: >-
|
|
needs.changes.outputs.has_e2e_scope == 'true' &&
|
|
needs.webui-v2-smoke.result == 'success'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
env:
|
|
IRONCLAW_HERMETIC_SUITE_SKIP_PREPARE: "1"
|
|
strategy:
|
|
fail-fast: false
|
|
max-parallel: 4
|
|
matrix:
|
|
include:
|
|
- lane: browser
|
|
kind: browser
|
|
shard: ""
|
|
- lane: provider-contracts
|
|
kind: provider
|
|
shard: ""
|
|
- lane: provider-0-1
|
|
kind: provider
|
|
shard: "0/4 1/4"
|
|
- lane: provider-2-3
|
|
kind: provider
|
|
shard: "2/4 3/4"
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
|
|
with:
|
|
ref: ${{ inputs.ref || (github.event_name == 'pull_request' && github.event.pull_request.head.sha) || github.sha }}
|
|
persist-credentials: false
|
|
|
|
# This lane runs a prebuilt binary and compiles nothing, but
|
|
# run-hermetic-test-process.sh probes `rustc --print sysroot` to put the
|
|
# toolchain's bin on the child PATH and exits 1 if it cannot resolve one.
|
|
# Inside the repo that probe hits rust-toolchain.toml, so rustup installed
|
|
# the pinned toolchain lazily, mid-lane, once per shard — which is where
|
|
# `component download failed for rustfmt: could not rename 'downloaded'
|
|
# file` came from. Installing once, up front, as a real step removes the
|
|
# lazy install; every other hermetic lane already does this.
|
|
- name: Install Rust
|
|
uses: ./.github/actions/setup-rust
|
|
|
|
- name: Download default Reborn E2E binary
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: reborn-webui-v2-binary-${{ needs.webui-v2-smoke.outputs.product_ref }}
|
|
path: artifacts/reborn-webui-v2-binary
|
|
|
|
- name: Download Reborn SSO test binary
|
|
if: matrix.kind == 'browser'
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: reborn-webui-v2-sso-binary-${{ needs.webui-v2-smoke.outputs.product_ref }}
|
|
path: artifacts/reborn-webui-v2-sso-binary
|
|
|
|
- name: Restore Reborn E2E binaries
|
|
run: |
|
|
mkdir -p target/debug
|
|
(
|
|
cd artifacts/reborn-webui-v2-binary
|
|
sha256sum -c ironclaw-reborn.tar.gz.sha256
|
|
)
|
|
tar -C target/debug -xzf \
|
|
artifacts/reborn-webui-v2-binary/ironclaw-reborn.tar.gz ironclaw
|
|
chmod +x target/debug/ironclaw
|
|
# The archive preserves build time, while checkout uses current time.
|
|
# Mark this same-commit binary fresh so E2E fixtures do not rebuild it.
|
|
touch target/debug/ironclaw
|
|
if [[ "${{ matrix.kind }}" == "browser" ]]; then
|
|
mkdir -p target/e2e-sso/debug
|
|
tar -C target/e2e-sso/debug -xzf \
|
|
artifacts/reborn-webui-v2-sso-binary/ironclaw-sso.tar.gz ironclaw
|
|
chmod +x target/e2e-sso/debug/ironclaw
|
|
touch target/e2e-sso/debug/ironclaw
|
|
fi
|
|
touch target/debug/.ironclaw-reborn-openai-compat.stamp
|
|
|
|
- name: Install Python
|
|
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
with:
|
|
python-version: "3.12"
|
|
cache: pip
|
|
cache-dependency-path: tests/e2e/pyproject.toml
|
|
|
|
- name: Install E2E dependencies
|
|
run: |
|
|
cd tests/e2e
|
|
pip install -e .
|
|
|
|
- name: Configure Playwright
|
|
if: matrix.kind == 'browser'
|
|
run: echo "PLAYWRIGHT_BROWSERS_PATH=${RUNNER_TEMP}/ms-playwright" >> "$GITHUB_ENV"
|
|
|
|
- name: Cache Playwright browsers
|
|
if: matrix.kind == 'browser'
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ${{ runner.temp }}/ms-playwright
|
|
key: ${{ runner.os }}-playwright-${{ hashFiles('tests/e2e/pyproject.toml') }}
|
|
restore-keys: ${{ runner.os }}-playwright-
|
|
|
|
- name: Install Playwright browser
|
|
if: matrix.kind == 'browser'
|
|
# `playwright install --with-deps` shells out to apt as root, so it
|
|
# inherits the same stall-forever failure as the mold/clang step. The
|
|
# cache above covers only the browser tarball -- a cache hit still runs
|
|
# the apt transaction -- so bound the whole thing.
|
|
run: timeout --signal=INT --kill-after=30s 8m playwright install --with-deps chromium
|
|
|
|
- name: Checkout pinned Emulate fork
|
|
if: matrix.kind == 'provider'
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
|
|
with:
|
|
repository: serrrfirat/emulate
|
|
ref: c85da1ed9b4e1c9cbdcd9b9ec7af2aac93f5ea9e
|
|
path: .emulate
|
|
persist-credentials: false
|
|
|
|
- name: Enable pnpm for provider cache
|
|
if: matrix.kind == 'provider'
|
|
run: corepack enable pnpm
|
|
|
|
- name: Install Node.js for Emulate
|
|
if: matrix.kind == 'provider'
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v4
|
|
with:
|
|
node-version: "24"
|
|
cache: "pnpm"
|
|
cache-dependency-path: .emulate/pnpm-lock.yaml
|
|
|
|
- name: Build pinned Emulate fork
|
|
if: matrix.kind == 'provider'
|
|
working-directory: .emulate
|
|
run: |
|
|
pnpm install --frozen-lockfile
|
|
pnpm exec turbo build --filter=emulate...
|
|
|
|
- name: Run Reborn E2E lane
|
|
env:
|
|
LANE: ${{ matrix.lane }}
|
|
PROVIDER_SHARD: ${{ matrix.shard }}
|
|
IRONCLAW_EMULATE_CLI: ${{ github.workspace }}/.emulate/packages/emulate/dist/index.js
|
|
IRONCLAW_E2E_ARTIFACT_DIR: ${{ runner.temp }}/e2e-artifacts
|
|
run: |
|
|
set -euo pipefail
|
|
case "${LANE}" in
|
|
browser)
|
|
scripts/ci/run-hermetic-deterministic-suite.sh command pytest \
|
|
tests/e2e/scenarios/test_reborn_webui_v2_smoke.py \
|
|
tests/e2e/scenarios/test_reborn_webui_v2_sso.py \
|
|
tests/e2e/scenarios/test_reborn_webui_v2_custom_mcp.py \
|
|
-v --timeout=120
|
|
mapfile -t responses_tests < <(
|
|
sed -e 's/#.*//' -e '/^[[:space:]]*$/d' \
|
|
tests/e2e/reborn_responses_e2e_tests.txt
|
|
)
|
|
((${#responses_tests[@]} > 0))
|
|
scripts/ci/run-hermetic-deterministic-suite.sh command pytest \
|
|
"${responses_tests[@]}" -v --timeout=120
|
|
scripts/ci/run-hermetic-deterministic-suite.sh command pytest \
|
|
tests/e2e/scenarios/test_reborn_blackbox_smoke.py \
|
|
-v --timeout=120
|
|
;;
|
|
provider-contracts)
|
|
scripts/ci/run-hermetic-deterministic-suite.sh command pytest \
|
|
tests/e2e/scenarios/test_provider_capability_inventory.py \
|
|
tests/e2e/scenarios/test_journey_coverage.py \
|
|
tests/e2e/scenarios/test_emulate_reborn_provider_contracts.py \
|
|
tests/e2e/scenarios/test_provider_fault_proxy.py \
|
|
tests/e2e/scenarios/test_emulate_build_parity.py \
|
|
tests/e2e/scenarios/test_provider_world_isolation.py \
|
|
tests/e2e/scenarios/test_reborn_qa_trace_replay.py \
|
|
tests/e2e/scenarios/test_reborn_qa_trace_full_path.py \
|
|
-k "not provider_operation_case_executes_with_provider_readback" \
|
|
-m "not shared_world" -v --timeout=120
|
|
;;
|
|
provider-*)
|
|
read -ra provider_shards <<< "${PROVIDER_SHARD}"
|
|
((${#provider_shards[@]} > 0))
|
|
provider_pids=()
|
|
provider_logs=()
|
|
for provider_shard in "${provider_shards[@]}"; do
|
|
provider_log="${RUNNER_TEMP}/provider-operation-${provider_shard//\//-}.log"
|
|
(
|
|
IRONCLAW_PROVIDER_OPERATION_SHARD="${provider_shard}" \
|
|
scripts/ci/run-hermetic-deterministic-suite.sh command pytest \
|
|
tests/e2e/scenarios/test_reborn_qa_trace_full_path.py::test_provider_operation_case_executes_with_provider_readback \
|
|
-m "not shared_world" -p no:cacheprovider -v --timeout=120
|
|
) > "${provider_log}" 2>&1 &
|
|
provider_pids+=("$!")
|
|
provider_logs+=("${provider_log}")
|
|
done
|
|
|
|
provider_status=0
|
|
for index in "${!provider_pids[@]}"; do
|
|
provider_shard="${provider_shards[$index]}"
|
|
echo "::group::provider operation shard ${provider_shard}"
|
|
shard_status=0
|
|
wait "${provider_pids[$index]}" || shard_status=$?
|
|
cat "${provider_logs[$index]}"
|
|
if ((shard_status != 0)); then
|
|
echo "provider operation shard ${provider_shard} failed with status ${shard_status}" >&2
|
|
provider_status=1
|
|
fi
|
|
echo "::endgroup::"
|
|
done
|
|
exit "${provider_status}"
|
|
;;
|
|
*)
|
|
echo "Unknown Reborn E2E lane: ${LANE}" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
- name: Replay provider journeys in reverse order
|
|
if: matrix.lane == 'provider-0-1' && inputs.reversed_journey_order
|
|
env:
|
|
IRONCLAW_EMULATE_CLI: ${{ github.workspace }}/.emulate/packages/emulate/dist/index.js
|
|
IRONCLAW_JOURNEY_ORDER: reverse
|
|
run: >-
|
|
scripts/ci/run-hermetic-deterministic-suite.sh
|
|
command
|
|
pytest
|
|
tests/e2e/scenarios/test_reborn_qa_trace_full_path.py::test_mutating_qa_journeys_replay_in_reverse_against_shared_provider_world
|
|
-v
|
|
--timeout=120
|
|
|
|
- name: Replay each mutating journey alone
|
|
if: matrix.lane == 'provider-0-1' && inputs.alone_journey_replay
|
|
env:
|
|
IRONCLAW_EMULATE_CLI: ${{ github.workspace }}/.emulate/packages/emulate/dist/index.js
|
|
run: |
|
|
set -euo pipefail
|
|
journey_list="$(python scripts/ci/list_mutating_journeys.py)"
|
|
if [[ -z "${journey_list//[[:space:]]/}" ]]; then
|
|
echo "mutating journey list is empty" >&2
|
|
exit 1
|
|
fi
|
|
mapfile -t journeys <<<"${journey_list}"
|
|
echo "replaying ${#journeys[@]} journeys alone"
|
|
for journey in "${journeys[@]}"; do
|
|
echo "::group::alone: $journey"
|
|
scripts/ci/run-hermetic-deterministic-suite.sh command \
|
|
pytest tests/e2e/scenarios/test_reborn_qa_trace_full_path.py \
|
|
-k "$journey and not isolated" -v --timeout=120
|
|
echo "::endgroup::"
|
|
done
|
|
|
|
- name: Upload screenshots on failure
|
|
if: matrix.kind == 'browser' && failure()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: reborn-webui-v2-screenshots
|
|
path: tests/e2e/screenshots/
|
|
if-no-files-found: ignore
|
|
|
|
- name: Upload E2E diagnostics on failure
|
|
if: failure()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: reborn-e2e-${{ matrix.lane }}-diagnostics
|
|
path: ${{ runner.temp }}/e2e-artifacts/
|
|
if-no-files-found: ignore
|
|
|
|
reborn-e2e:
|
|
name: Reborn E2E
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
if: always()
|
|
needs:
|
|
- changes
|
|
- rust-reborn
|
|
- webui-v2-smoke
|
|
- webui-v2-test-lanes
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
|
|
with:
|
|
persist-credentials: false
|
|
- name: Check Reborn E2E jobs
|
|
run: |
|
|
# shellcheck disable=SC1091
|
|
if ! source .github/scripts/ci-job-result-ok.sh; then
|
|
echo "Unable to source .github/scripts/ci-job-result-ok.sh"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "${{ needs.changes.result }}" != "success" ]]; then
|
|
echo "changes (scope detection) failed: ${{ needs.changes.result }}"
|
|
exit 1
|
|
fi
|
|
if [[ "${{ needs.changes.outputs.has_e2e_scope }}" == "false" ]]; then
|
|
echo "No Reborn E2E scope in this merge group — roll-up passes"
|
|
exit 0
|
|
fi
|
|
rust_reborn_optional=false
|
|
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
|
rust_reborn_optional=true
|
|
fi
|
|
if ! job_result_ok "rust-reborn" "${{ needs.rust-reborn.result }}" "${rust_reborn_optional}" "allow"; then
|
|
echo "Rust Reborn E2E failed"
|
|
exit 1
|
|
fi
|
|
if ! job_result_ok "webui-v2-smoke" "${{ needs.webui-v2-smoke.result }}" false "allow"; then
|
|
echo "Reborn WebUI v2 build failed"
|
|
exit 1
|
|
fi
|
|
if ! job_result_ok "webui-v2-test-lanes" "${{ needs.webui-v2-test-lanes.result }}" false "allow"; then
|
|
echo "Reborn WebUI v2 E2E lanes failed"
|
|
exit 1
|
|
fi
|