ci: prebuild desktop E2E dependencies (#2789)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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-<content-hash>` 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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
+1
-1
@@ -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 |
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user