From 22472460a64c6eaa7311230b21a84cc4f458e5b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Flor=C3=AAncio?= Date: Wed, 19 Aug 2026 07:50:00 +0100 Subject: [PATCH] ci: prebuild desktop E2E dependencies (#2789) --- .github/docker/ci-base/Dockerfile | 48 ++++++- .../e2e-tests-workflow-contract_test.py | 107 ++++++++++++++ .github/workflows/ci-base-image.yml | 19 +++ .github/workflows/e2e-tests.yml | 43 +++--- .github/workflows/lint-action-pinning.yml | 3 + docs/plans/desktop-e2e-prebuilt-image/plan.md | 132 ++++++++++++++++++ .../task-01-prebuild-desktop-image.md | 76 ++++++++++ .../task-02-consume-and-prove-image.md | 81 +++++++++++ docs/specs/INDEX.md | 2 +- .../specs/e2e-duration-aware-sharding/spec.md | 42 +++++- 10 files changed, 516 insertions(+), 37 deletions(-) create mode 100644 .github/scripts/e2e-tests-workflow-contract_test.py create mode 100644 docs/plans/desktop-e2e-prebuilt-image/plan.md create mode 100644 docs/plans/desktop-e2e-prebuilt-image/task-01-prebuild-desktop-image.md create mode 100644 docs/plans/desktop-e2e-prebuilt-image/task-02-consume-and-prove-image.md diff --git a/.github/docker/ci-base/Dockerfile b/.github/docker/ci-base/Dockerfile index 4538692bc4..a1a3bd528f 100644 --- a/.github/docker/ci-base/Dockerfile +++ b/.github/docker/ci-base/Dockerfile @@ -1,6 +1,6 @@ -# Kandev CI base image — split into two tags. +# Kandev CI base image — split into three tags. # -# Two stages published as separate tags on the same GHCR repo: +# Three stages published as separate tags on the same GHCR repo: # # - `runtime` (~700 MB): Playwright + Node + pnpm + Docker CLI. Used by the # `e2e` shards and `e2e-report` — they consume pre-built backend artifacts @@ -11,6 +11,9 @@ # the `build` job (compiles backend + web), `backend-tests`, and # `frontend-tests`. These all need the Go toolchain. # +# - `desktop`: runtime + Tauri Linux dependencies + Rust. Used by the +# desktop smoke job, so hosted-runner package mirrors are not in its setup. +# # Pinned versions (keep in sync with apps/backend/go.mod, apps/web/package.json, # and the matching workflow inputs): # - Playwright base : v1.61.1-noble (matches @playwright/test ^1.61.1) @@ -20,6 +23,7 @@ # - golangci-lint : v2.9.0 (matches v2.9 floating pin) # - go-licenses : v1.6.0 # - Docker CLI : 27.3.1 +# - Rust : 1.97.1 (pinned for reproducible desktop CI) # ============================================================================= # Stage 1: runtime — Playwright + Node + pnpm + Docker CLI (no Go) @@ -105,3 +109,43 @@ RUN set -eux; \ go version | grep -F 'go1.26.0'; \ golangci-lint --version | grep -F '2.9.0'; \ go version -m "$(which go-licenses)" | grep -F 'v1.6.0' + +# ============================================================================= +# Stage 3: desktop — runtime + Tauri Linux dependencies + Rust +# ============================================================================= +FROM runtime AS desktop + +ARG RUST_VERSION=1.97.1 + +ENV CARGO_HOME=/root/.cargo \ + RUSTUP_HOME=/root/.rustup \ + PATH=/pnpm:/root/.cargo/bin:$PATH + +# Keep hosted-runner package mirrors out of the desktop smoke job. This stage +# also contains the compiler and rpm tooling used by the Tauri bundle step. +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + build-essential \ + pkg-config \ + libglib2.0-dev \ + libwebkit2gtk-4.1-dev \ + libgtk-3-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + patchelf \ + rpm \ + xvfb \ + && rm -rf /var/lib/apt/lists/* + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ + | sh -s -- -y --default-toolchain none --profile minimal \ + && rustup toolchain install "${RUST_VERSION}" --profile minimal \ + && rustup default "${RUST_VERSION}" + +# --- Desktop smoke checks -------------------------------------------------- +RUN set -eux; \ + rustc --version | grep -F "${RUST_VERSION}"; \ + cargo --version; \ + pkg-config --exists webkit2gtk-4.1; \ + command -v patchelf; \ + command -v xvfb-run diff --git a/.github/scripts/e2e-tests-workflow-contract_test.py b/.github/scripts/e2e-tests-workflow-contract_test.py new file mode 100644 index 0000000000..d988998b3d --- /dev/null +++ b/.github/scripts/e2e-tests-workflow-contract_test.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +"""Contract tests for the prebuilt desktop E2E image path.""" + +from pathlib import Path +import unittest + + +REPO_ROOT = Path(__file__).resolve().parents[2] +DOCKERFILE = REPO_ROOT / ".github" / "docker" / "ci-base" / "Dockerfile" +IMAGE_WORKFLOW = REPO_ROOT / ".github" / "workflows" / "ci-base-image.yml" +E2E_WORKFLOW = REPO_ROOT / ".github" / "workflows" / "e2e-tests.yml" +LINT_WORKFLOW = REPO_ROOT / ".github" / "workflows" / "lint-action-pinning.yml" + + +def job_block(workflow: str, job: str, next_job: str) -> str: + """Return one workflow job block without parsing YAML anchors or expressions.""" + marker = f" {job}:\n" + _, separator, remainder = workflow.partition(marker) + if not separator: + raise AssertionError(f"Workflow has no {job} job") + return remainder.partition(f"\n {next_job}:\n")[0] + + +class DesktopE2EWorkflowContractTest(unittest.TestCase): + def test_desktop_image_contains_pinned_toolchain_and_system_dependencies(self) -> None: + dockerfile = DOCKERFILE.read_text(encoding="utf-8") + + self.assertIn("FROM runtime AS desktop", dockerfile) + self.assertIn("ARG RUST_VERSION=1.97.1", dockerfile) + self.assertIn("rustup toolchain install \"${RUST_VERSION}\" --profile minimal", dockerfile) + + for package in ( + "build-essential", + "pkg-config", + "libglib2.0-dev", + "libwebkit2gtk-4.1-dev", + "libgtk-3-dev", + "libayatana-appindicator3-dev", + "librsvg2-dev", + "patchelf", + "rpm", + "xvfb", + ): + self.assertIn(package, dockerfile) + + for smoke_command in ( + "rustc --version", + "cargo --version", + "pkg-config --exists webkit2gtk-4.1", + "command -v patchelf", + "command -v xvfb-run", + ): + self.assertIn(smoke_command, dockerfile) + + def test_image_workflow_publishes_desktop_tags(self) -> None: + workflow = IMAGE_WORKFLOW.read_text(encoding="utf-8") + + self.assertIn("target: desktop", workflow) + self.assertIn("desktop-sha-${{ steps.tag.outputs.image_tag }}", workflow) + self.assertIn("${{ env.IMAGE_NAME }}:desktop-latest", workflow) + self.assertIn("type=gha,scope=desktop", workflow) + self.assertIn("type=gha,scope=runtime", workflow) + self.assertIn("desktop-latest", workflow) + + def test_desktop_job_uses_image_without_live_bootstrap_downloads(self) -> None: + workflow = E2E_WORKFLOW.read_text(encoding="utf-8") + desktop_job = job_block(workflow, "desktop-e2e", "e2e-report") + + self.assertIn("image: ghcr.io/kdlbs/kandev-ci:desktop-latest", desktop_job) + self.assertIn("options: --ipc=host", desktop_job) + self.assertIn("git config --global --add safe.directory", desktop_job) + self.assertIn("path: ~/.local/share/pnpm/store", desktop_job) + self.assertIn("pnpm install --frozen-lockfile", desktop_job) + self.assertIn("pnpm --filter @kandev/desktop e2e", desktop_job) + + for forbidden in ( + "pnpm/action-setup", + "actions/setup-node", + "rustup toolchain install", + "apt-get", + "sudo", + ): + self.assertNotIn(forbidden, desktop_job) + + changes_job = job_block(workflow, "changes", "build") + for pattern in ( + ".github/docker/ci-base/**", + ".github/workflows/ci-base-image.yml", + ): + self.assertIn(pattern, changes_job) + + def test_contract_runs_in_the_unfiltered_required_workflow(self) -> None: + workflow = LINT_WORKFLOW.read_text(encoding="utf-8") + + self.assertIn( + "python3 .github/scripts/e2e-tests-workflow-contract_test.py", + workflow, + ) + for trigger in ("push", "pull_request", "merge_group"): + trigger_marker = f" {trigger}:" + _, separator, trigger_block_text = workflow.partition(trigger_marker) + self.assertTrue(separator, f"Lint workflow has no {trigger} trigger") + self.assertNotIn(" paths:", trigger_block_text.split("\n ", 1)[0]) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/workflows/ci-base-image.yml b/.github/workflows/ci-base-image.yml index 1570873f8c..955ec76698 100644 --- a/.github/workflows/ci-base-image.yml +++ b/.github/workflows/ci-base-image.yml @@ -132,6 +132,24 @@ jobs: ${{ env.IMAGE_NAME }}:build-sha-${{ steps.tag.outputs.image_tag }} ${{ env.IMAGE_NAME }}:build-latest + # Stage 3: desktop (runtime + Tauri Linux dependencies + Rust). + # Consumed by the host-independent desktop smoke job. + - name: Build and push desktop + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + context: . + file: .github/docker/ci-base/Dockerfile + target: desktop + platforms: linux/amd64 + push: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main') }} + cache-from: | + type=gha,scope=desktop + type=gha,scope=runtime + cache-to: type=gha,scope=desktop,mode=max + tags: | + ${{ env.IMAGE_NAME }}:desktop-sha-${{ steps.tag.outputs.image_tag }} + ${{ env.IMAGE_NAME }}:desktop-latest + - name: Summarize run: | { @@ -139,6 +157,7 @@ jobs: echo "" echo "- runtime: \`${IMAGE_NAME}:runtime-sha-${IMAGE_TAG}\` / \`runtime-latest\`" echo "- build: \`${IMAGE_NAME}:build-sha-${IMAGE_TAG}\` / \`build-latest\`" + echo "- desktop: \`${IMAGE_NAME}:desktop-sha-${IMAGE_TAG}\` / \`desktop-latest\`" echo "- postgres: \`${POSTGRES_TARGET}@${POSTGRES_DIGEST}\`" echo "- pushed: \`${PUSHED}\`" } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index d1eadaae91..cb78d202e0 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -67,6 +67,8 @@ jobs: apps/packages/** apps/pnpm-lock.yaml apps/pnpm-workspace.yaml + .github/docker/ci-base/** + .github/workflows/ci-base-image.yml .github/workflows/e2e-tests.yml .github/scripts/changed-paths.py !**/*.md @@ -602,8 +604,11 @@ jobs: needs: changes if: needs.changes.outputs.run == 'true' runs-on: ubuntu-latest - # Includes Ubuntu WebKit/Tauri system package installation; GitHub apt mirrors - # can spend more than 20 minutes there before tests start. + # The desktop image contains the Rust toolchain, Tauri dependencies, and + # Xvfb. This job does not depend on hosted-runner apt mirrors. + container: + image: ghcr.io/kdlbs/kandev-ci:desktop-latest + options: --ipc=host timeout-minutes: 45 defaults: run: @@ -615,20 +620,17 @@ jobs: with: persist-credentials: false - - name: Set up pnpm - uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5 - with: - version: "9.15.9" + - name: Mark workspace safe for git + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - name: Set up Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + # The desktop image uses /root as HOME, so this matches the store path + # used by the other container jobs. + - name: Cache pnpm store + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - node-version: "24" - cache: pnpm - cache-dependency-path: apps/pnpm-lock.yaml - - - name: Set up Rust - run: rustup toolchain install stable --profile minimal + path: ~/.local/share/pnpm/store + key: pnpm-${{ runner.os }}-${{ hashFiles('apps/pnpm-lock.yaml') }} + restore-keys: pnpm-${{ runner.os }}- - name: Cache Rust build uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2 @@ -636,19 +638,6 @@ jobs: workspaces: apps/desktop/src-tauri -> target key: desktop-e2e - - name: Install desktop system dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - pkg-config \ - libglib2.0-dev \ - libwebkit2gtk-4.1-dev \ - libgtk-3-dev \ - libayatana-appindicator3-dev \ - librsvg2-dev \ - patchelf \ - xvfb - - name: Install dependencies working-directory: apps run: pnpm install --frozen-lockfile diff --git a/.github/workflows/lint-action-pinning.yml b/.github/workflows/lint-action-pinning.yml index a421b9bde2..ab571bb6a1 100644 --- a/.github/workflows/lint-action-pinning.yml +++ b/.github/workflows/lint-action-pinning.yml @@ -59,6 +59,9 @@ jobs: - name: Test release workflow contract run: python3 .github/scripts/release-workflow-contract_test.py + - name: Test E2E workflow contract + run: python3 .github/scripts/e2e-tests-workflow-contract_test.py + - name: Set up Node.js uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: diff --git a/docs/plans/desktop-e2e-prebuilt-image/plan.md b/docs/plans/desktop-e2e-prebuilt-image/plan.md new file mode 100644 index 0000000000..fd5851c803 --- /dev/null +++ b/docs/plans/desktop-e2e-prebuilt-image/plan.md @@ -0,0 +1,132 @@ +--- +spec: docs/specs/e2e-duration-aware-sharding/spec.md +created: 2026-08-18 +status: complete +--- + +# Implementation Plan: Prebuilt Desktop E2E Image + +## Overview + +Merge-queue run `32170420828` confirmed that `apt-get update` can stall until +the 45-minute desktop job limit expires. The repair moves stable system and +Rust toolchain dependencies into a dedicated GHCR image. The desktop smoke job +then starts from that image and retains only source-specific installation, +build, and test work. + +## CI image + +### Desktop stage + +Extend `.github/docker/ci-base/Dockerfile` with a `desktop` stage based on the +existing `runtime` stage. Install these items while the image is built: + +- Rust `1.97.1` with the minimal profile, +- `build-essential` and `rpm` for the Tauri bundle, +- `pkg-config`, +- `libglib2.0-dev`, +- `libwebkit2gtk-4.1-dev`, +- `libgtk-3-dev`, +- `libayatana-appindicator3-dev`, +- `librsvg2-dev`, +- `patchelf`, and +- `xvfb`. + +Set `RUST_VERSION=1.97.1`, `CARGO_HOME=/root/.cargo`, and the Rust path in the +image. Add image-build smoke commands for `rustc`, `cargo`, +`pkg-config webkit2gtk-4.1`, `patchelf`, and `xvfb-run`. Do not add Go or the +backend build tools to this stage. + +### Image publisher + +Extend `.github/workflows/ci-base-image.yml` with one Buildx invocation for the +`desktop` target. Publish `desktop-sha-` and `desktop-latest` with +the same rules as the existing images. Use a `desktop` cache scope for new +layers and the `runtime` scope as a lower-layer source. Add the desktop tag to +the workflow summary. + +## Desktop smoke workflow + +Update the `desktop-e2e` job in `.github/workflows/e2e-tests.yml` to use +`ghcr.io/kdlbs/kandev-ci:desktop-latest` as its job container. Add the safe Git +directory step and the pnpm-store cache pattern from the existing container +jobs. Use `--ipc=host` so WebKit receives the same shared-memory configuration +as the validated local smoke command. Include the image Dockerfile and +publisher workflow in the job's in-workflow change patterns. + +Remove the pnpm setup, Node.js setup, Rust setup, and system-package steps. +Keep the Rust build cache, workspace install, and desktop smoke command. Reduce +the timeout only if a measured image-based run supports a smaller limit. + +## Tests + +- **What:** the desktop image contains the required packages and toolchain, and + the publisher emits both desktop tags. + **File:** `.github/scripts/e2e-tests-workflow-contract_test.py` + **How:** add textual contract assertions for the Docker stage, package list, + smoke commands, Buildx target, cache scopes, tags, and summary. +- **What:** the desktop smoke job consumes the desktop image without live + system-package or Rust toolchain installation. + **File:** `.github/scripts/e2e-tests-workflow-contract_test.py` + **How:** isolate the `desktop-e2e` job and assert its image, caches, retained + smoke command, and forbidden setup commands. +- **What:** the contract test remains part of a required, unfiltered check. + **File:** `.github/workflows/lint-action-pinning.yml` + **How:** add the contract test to the existing workflow-contract test steps. +- **What:** the desktop job keeps the validated IPC setting and runs when its + image inputs change. + **File:** `.github/scripts/e2e-tests-workflow-contract_test.py` + **How:** assert `options: --ipc=host` and the image paths in the E2E change + detection block. + +The regression test must fail against the current workflow because the desktop +image and its publisher do not exist. It must also report the current live +`apt-get` and `rustup` setup as contract errors. + +## Rollout + +The new `desktop-latest` tag does not exist before this change. After the +implementation branch is pushed, start `ci-base-image.yml` with +`workflow_dispatch` on that branch. Wait for a successful image publish before +the pull request E2E workflow consumes the tag. Use the workflow publisher for +this bootstrap. Do not create or retag the image manually. + +The first pull request E2E run must show that `Desktop E2E Smoke` enters the +container and runs the desktop command. Its log must contain no Ubuntu mirror +or Rust toolchain download step. + +## Verification Results + +Local contract, action-pinning, Dockerfile, and desktop smoke checks pass. The +branch image publisher run `32191451396` completed successfully, including the +`Build and push desktop` job `95886471809`. PR E2E run `32188852911` attempt 2 +completed successfully; `Desktop E2E Smoke` job `95887857830` entered the +published container, built DEB/RPM bundles, and reported a successful WebView +smoke. Its log contains source dependency installation but no live apt or Rust +toolchain setup. + +## Implementation Waves And Parallel Candidates + +Execute sequentially in the primary session: + +### Wave 1 + +- [x] [task-01-prebuild-desktop-image](task-01-prebuild-desktop-image.md) + +### Wave 2 + +- [x] [task-02-consume-and-prove-image](task-02-consume-and-prove-image.md) + +## Risks + +- GitHub job containers can expose display or WebKit behavior that differs + from the hosted runner. The image-based desktop smoke run is the acceptance + test for this boundary. +- A new GHCR tag creates a bootstrap dependency. The documented branch + dispatch resolves it before the consumer check runs. +- pnpm and Cargo can still contact their registries on a cold cache. This + repair removes the unbounded operating-system and toolchain setup path only. + +## Open Questions + +None. diff --git a/docs/plans/desktop-e2e-prebuilt-image/task-01-prebuild-desktop-image.md b/docs/plans/desktop-e2e-prebuilt-image/task-01-prebuild-desktop-image.md new file mode 100644 index 0000000000..6885f10477 --- /dev/null +++ b/docs/plans/desktop-e2e-prebuilt-image/task-01-prebuild-desktop-image.md @@ -0,0 +1,76 @@ +--- +id: "01-prebuild-desktop-image" +title: "Prebuild desktop CI dependencies" +status: done +wave: 1 +depends_on: [] +plan: "plan.md" +spec: "../../specs/e2e-duration-aware-sharding/spec.md" +--- + +# Task 01: Prebuild Desktop CI Dependencies + +## Acceptance + +- A dedicated `desktop` image stage contains Rust `1.97.1`, the compiler and + rpm bundle tools, and all Linux packages that the current desktop smoke job + installs. +- The CI image workflow publishes content-addressed and `desktop-latest` tags + through the existing GHCR path. +- A workflow contract test fails if the image stage, dependencies, smoke + commands, publisher, or required test invocation disappears. + +## Verification + +```bash +python3 .github/scripts/e2e-tests-workflow-contract_test.py +python3 .github/scripts/lint-action-pinning_test.py +python3 .github/scripts/lint-action-pinning.py +docker build --target desktop --tag kandev-ci:desktop-local --file .github/docker/ci-base/Dockerfile . +``` + +## Files likely touched + +- `.github/docker/ci-base/Dockerfile` +- `.github/workflows/ci-base-image.yml` +- `.github/scripts/e2e-tests-workflow-contract_test.py` +- `.github/workflows/lint-action-pinning.yml` + +## Dependencies + +None. + +## Parallelism + +sequential + +## Inputs + +- `docs/specs/e2e-duration-aware-sharding/spec.md`, especially the CI lifecycle + and reliability contract. +- `docs/plans/desktop-e2e-prebuilt-image/plan.md`, CI image and Tests sections. +- The `runtime` and `build` patterns in the current CI base Dockerfile and + publisher workflow. + +## Output contract + +Report the RED contract result, image contents, exact command results, changed +files, blockers, risks, and the synchronized task and plan status. + +## Results + +- RED: `python3 .github/scripts/e2e-tests-workflow-contract_test.py` failed with + the expected missing desktop stage, publisher, consumer, and lint invocation. +- GREEN: the same contract test passed (`4 tests`). +- `python3 .github/scripts/lint-action-pinning_test.py` passed (`9 tests`). +- `python3 .github/scripts/lint-action-pinning.py` passed for all 18 workflows. +- `docker build --target desktop --tag kandev-ci:desktop-local --file + .github/docker/ci-base/Dockerfile .` passed. The image smoke checks reported + Rust 1.97.1, WebKitGTK, patchelf, and Xvfb. +- Changed files: the desktop Dockerfile, CI image publisher, E2E workflow, + action-pinning workflow, contract test, and the associated plan/spec/task + records. +- Blockers: none after the authorized branch image dispatch. +- Risks: cold pnpm/Cargo project dependency downloads still depend on external + registries; the unbounded OS and Rust bootstrap path is prebuilt. +- Synchronized status: Task 01 is done and Wave 1 is complete in the plan. diff --git a/docs/plans/desktop-e2e-prebuilt-image/task-02-consume-and-prove-image.md b/docs/plans/desktop-e2e-prebuilt-image/task-02-consume-and-prove-image.md new file mode 100644 index 0000000000..3687f91ec5 --- /dev/null +++ b/docs/plans/desktop-e2e-prebuilt-image/task-02-consume-and-prove-image.md @@ -0,0 +1,81 @@ +--- +id: "02-consume-and-prove-image" +title: "Consume and prove desktop image" +status: done +wave: 2 +depends_on: ["01-prebuild-desktop-image"] +plan: "plan.md" +spec: "../../specs/e2e-duration-aware-sharding/spec.md" +--- + +# Task 02: Consume And Prove The Desktop Image + +## Acceptance + +- `Desktop E2E Smoke` uses `ghcr.io/kdlbs/kandev-ci:desktop-latest` and contains + no live Node.js, pnpm, Rust toolchain, or Ubuntu package setup step. +- The container uses `--ipc=host`, and E2E change detection includes the image + Dockerfile and publisher workflow. +- The job retains the pnpm and Rust caches, workspace install, and real desktop + smoke command. +- The branch image publish and the image-based desktop smoke job succeed, with + no Ubuntu mirror or Rust toolchain download in the smoke-job log. + +## Verification + +```bash +python3 .github/scripts/e2e-tests-workflow-contract_test.py +python3 .github/scripts/lint-action-pinning_test.py +python3 .github/scripts/lint-action-pinning.py +docker run --rm --ipc=host --volume "$PWD:/work" --workdir /work/apps kandev-ci:desktop-local bash -lc 'pnpm install --frozen-lockfile && pnpm --filter @kandev/desktop e2e' +``` + +The branch image publisher run `32191451396` succeeded. The PR E2E run +`32188852911` attempt 2 succeeded after rerunning its failed jobs; its +`Desktop E2E Smoke` job `95887857830` passed container initialization, +dependency installation, DEB/RPM bundling, and the WebView smoke. + +## Files likely touched + +- `.github/workflows/e2e-tests.yml` +- `.github/scripts/e2e-tests-workflow-contract_test.py` + +## Dependencies + +Task 01. The remote smoke check also requires the branch image publish. + +## Parallelism + +sequential + +## Inputs + +- `docs/specs/e2e-duration-aware-sharding/spec.md`, especially the desktop + smoke bootstrap requirements. +- `docs/plans/desktop-e2e-prebuilt-image/plan.md`, Desktop smoke workflow, + Rollout, and Risks sections. +- The pnpm and safe-directory patterns in the existing container jobs. + +## Output contract + +Report the RED and GREEN contract results, local image smoke result, GHCR +publish run, Actions desktop smoke result, changed files, blockers, risks, and +the synchronized task and plan status. + +## Results + +- The desktop workflow contract passed (`4 tests`) after the image consumer was + added. +- Review remediation added the host IPC setting and image paths to change + detection; the contract test first failed on the missing IPC mapping and + then passed after both changes. +- The local desktop image smoke passed in the container. `pnpm install + --frozen-lockfile` completed, the Tauri application built both DEB and RPM + bundles, and `node e2e/desktop-launch-smoke.mjs` reported a successful + WebView request after backend health. +- Remote image publication and pull-request verification are complete. The + smoke log records `pnpm install --frozen-lockfile`, successful DEB/RPM + bundles, and `Desktop smoke passed: WebView requested / after backend health`; + it contains no live apt or Rust toolchain setup. +- Changed files, blockers, risks, and synchronized plan status are recorded in + this task and the parent plan. diff --git a/docs/specs/INDEX.md b/docs/specs/INDEX.md index a8cf81e52d..10a691e075 100644 --- a/docs/specs/INDEX.md +++ b/docs/specs/INDEX.md @@ -296,7 +296,7 @@ System pages (Radarr/Sonarr-style) for status, disk usage, database maintenance, | [automation-runs-delete-all-by-status](automation-runs-delete-all-by-status/spec.md) | draft | | [no-silent-model-fallback](no-silent-model-fallback/spec.md) | approved | | [portable-agent-configuration](portable-agent-configuration/spec.md) | draft | -| [e2e-duration-aware-sharding](e2e-duration-aware-sharding/spec.md) | implemented | +| [e2e-duration-aware-sharding](e2e-duration-aware-sharding/spec.md) | shipped | | [board-step-visibility-filter](board-step-visibility-filter/spec.md) | draft | | [shutdown-turn-failure-suppression](shutdown-turn-failure-suppression/spec.md) | draft | | [executor-profile-env-precedence](executor-profile-env-precedence/spec.md) | building | diff --git a/docs/specs/e2e-duration-aware-sharding/spec.md b/docs/specs/e2e-duration-aware-sharding/spec.md index a53a4e97b8..b692049efd 100644 --- a/docs/specs/e2e-duration-aware-sharding/spec.md +++ b/docs/specs/e2e-duration-aware-sharding/spec.md @@ -1,6 +1,7 @@ --- -status: implemented +status: shipped created: 2026-08-10 +updated: 2026-08-18 --- # Duration-aware E2E sharding and CI reliability @@ -24,6 +25,12 @@ This capability makes shard assignment adapt to the suite as it grows. It also turns the known reliability findings into deterministic regression work and makes passed-after-retry results visible. +A merge-queue run later exposed a separate bootstrap problem in the desktop +smoke job. The job contacted Ubuntu package mirrors for WebKit and Tauri build +packages during each run. A mirror stopped responding, so the job reached its +45-minute limit before the desktop smoke test started. The aggregate E2E gate +then removed a valid pull request from the merge queue. + ## What The E2E workflow gains two duration-aware cohorts: @@ -107,6 +114,11 @@ contract. retry summary, prediction-versus-actual summary, and plan health counters. 5. Only a successful `main` result is eligible to become the next baseline. +The desktop smoke job uses a dedicated image from `ghcr.io/kdlbs/kandev-ci`. +The image contains Node.js, pnpm, Rust, Xvfb, and the Linux packages that Tauri +needs. The job does not contact Ubuntu or Rust toolchain servers during setup. +It can still use the existing pnpm and Cargo caches for project dependencies. + If the `main` artifact is unavailable, the workflow continues with the validated count-based fallback and records that fallback in the report. A missing or malformed manifest is a hard planning failure, not permission to @@ -132,6 +144,14 @@ The desktop and mobile multi-PR failures share the same fixture defect and must be fixed together. Each repaired failure gets a regression assertion that fails for the observed cause, not only a larger timeout. +The desktop smoke bootstrap has these requirements: + +- the CI image build installs the Linux and Rust toolchain dependencies, +- the image publisher produces content-addressed and `desktop-latest` tags, +- the desktop smoke job uses the published image, +- the job contains no live `apt-get` or `rustup toolchain install` step, and +- a workflow contract test protects the image producer and consumer together. + Playwright output also includes a retry summary containing the test key, attempt count, final status, error context, and links to retry artifacts. The normal PR lane may retain its current retry gate while the summary is being @@ -168,6 +188,10 @@ they improve wall time without increasing flakes. visible in the retry summary with actionable evidence. - The timing profile can be unavailable without blocking a correct, deterministic fallback plan. +- The desktop smoke job reaches its build and test commands without contacting + Ubuntu package mirrors or Rust toolchain servers. +- A contract test fails if the desktop image publisher disappears or the smoke + job restores live system-package or toolchain installation. ## Out of scope @@ -176,12 +200,16 @@ they improve wall time without increasing flakes. - committing per-shard test lists that require manual maintenance; - moving timing data to an external database; - globally deleting Docker resources to make a test pass; -- masking flakes by increasing timeouts or retries. +- masking flakes by increasing timeouts or retries; +- removing network access that project package installation or source checkout + still requires. ## Implementation status -Implemented in the CI workflow, E2E tooling, scoped Docker fixtures, and -focused reliability tests. The generated manifests, rolling profile, retry -diagnostics, and fail-closed runner are covered by unit/type checks and local -targeted E2E runs. The three-successful-main-run balance target remains an -operational check after merge. +The duration-aware sharding work is implemented in the CI workflow, E2E +tooling, scoped Docker fixtures, and focused reliability tests. The generated +manifests, rolling profile, retry diagnostics, and fail-closed runner have +unit, type, and targeted E2E coverage. The prebuilt desktop image amendment is +implemented and was proven by the branch image publish and pull-request smoke +run recorded in `docs/plans/desktop-e2e-prebuilt-image/plan.md`. The balance +target remains an operational check after merge.