diff --git a/.changeset/reliable-account-switching.md b/.changeset/reliable-account-switching.md
new file mode 100644
index 000000000..867e1fe88
--- /dev/null
+++ b/.changeset/reliable-account-switching.md
@@ -0,0 +1,5 @@
+---
+"executor": patch
+---
+
+Make multi-account server profiles reliable across the CLI and desktop, including same-origin switching, unambiguous logout, persistent remote selection, and immediate self-host membership revocation.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 937936e3b..c8a4f7aca 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -73,45 +73,3 @@ jobs:
- run: bun install --frozen-lockfile
- run: bun run test
-
- desktop-smoke:
- name: Desktop smoke build
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - uses: oven-sh/setup-bun@v2
- with:
- bun-version: 1.3.11
-
- - run: bun install --frozen-lockfile
-
- - name: Build web app
- run: bun run --filter @executor-js/local build
-
- - name: Build bundled executor
- env:
- BUN_TARGET: bun-linux-x64
- run: bun ./scripts/build-sidecar.ts
- working-directory: apps/desktop
-
- - name: Build Electron main/preload/renderer
- run: bunx --bun electron-vite build
- working-directory: apps/desktop
-
- selfhost-docker-smoke:
- name: Self-host Docker image
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
-
- - name: Build self-host image
- uses: docker/build-push-action@v6
- with:
- context: .
- file: apps/host-selfhost/Dockerfile
- push: false
- tags: executor-selfhost:ci
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
new file mode 100644
index 000000000..452b4daa8
--- /dev/null
+++ b/.github/workflows/e2e.yml
@@ -0,0 +1,669 @@
+name: End-to-end
+
+on:
+ pull_request:
+ push:
+ branches:
+ - main
+ schedule:
+ - cron: "23 7 * * *"
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: e2e-${{ github.event_name }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name != 'schedule' }}
+
+jobs:
+ portable:
+ name: Portable / ${{ matrix.project }}
+ runs-on: ubuntu-latest
+ timeout-minutes: 45
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - project: harness
+ script: test:harness
+ browser: false
+ claude: false
+ opencode: false
+ - project: clients
+ script: test:clients
+ browser: false
+ claude: true
+ opencode: false
+ - project: cloud-hermetic
+ script: test:cloud:hermetic
+ browser: true
+ claude: true
+ opencode: true
+ - project: selfhost-hermetic
+ script: test:selfhost:hermetic
+ browser: true
+ claude: true
+ opencode: true
+ - project: cloudflare-hermetic
+ script: test:cloudflare:hermetic
+ browser: true
+ claude: false
+ opencode: false
+ - project: local
+ script: test:local
+ browser: true
+ claude: false
+ opencode: false
+ env:
+ E2E_REQUIRED_CAPABILITY_MODE: required
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: 1.3.11
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Install browser runtime
+ if: matrix.browser
+ run: bunx playwright install --with-deps chromium
+
+ - name: Install pinned Claude Code client
+ if: matrix.claude
+ shell: bash
+ run: |
+ bun install --global @anthropic-ai/claude-code@2.1.195
+ echo "E2E_CLAUDE_CODE_BIN=$(command -v claude)" >> "$GITHUB_ENV"
+ echo "E2E_CLAUDE_CODE_REQUIRED=1" >> "$GITHUB_ENV"
+ echo "E2E_CLAUDE_CODE_VERSION=2.1.195" >> "$GITHUB_ENV"
+
+ - name: Install pinned OpenCode client
+ if: matrix.opencode
+ shell: bash
+ run: |
+ bun install --global opencode-ai@1.17.11
+ command -v opencode
+ echo "E2E_OPENCODE_REQUIRED=1" >> "$GITHUB_ENV"
+
+ - name: Run ${{ matrix.project }}
+ run: bun run --cwd e2e ${{ matrix.script }}
+
+ - name: Sanitize evidence
+ id: sanitize
+ if: always()
+ run: bun e2e/scripts/sanitize-evidence.ts --trusted-project "${{ matrix.project }}"
+
+ - name: Upload evidence
+ if: always() && steps.sanitize.outcome == 'success'
+ uses: actions/upload-artifact@v4
+ with:
+ name: e2e-${{ matrix.project }}-${{ github.run_attempt }}
+ path: e2e/runs
+ if-no-files-found: ignore
+ retention-days: 14
+
+ scheduled-deterministic-gate:
+ name: Scheduled deterministic coverage is required
+ if: ${{ always() && github.event_name == 'schedule' }}
+ needs: portable
+ runs-on: ubuntu-latest
+ env:
+ PORTABLE_RESULT: ${{ needs.portable.result }}
+ steps:
+ - name: Require a successful deterministic lane
+ shell: bash
+ run: |
+ if [[ "$PORTABLE_RESULT" != "success" ]]; then
+ echo "::error::Scheduled e2e requires the deterministic portable matrix, got: $PORTABLE_RESULT"
+ exit 1
+ fi
+
+ selfhost-production:
+ name: Portable / selfhost production image
+ if: github.event_name != 'schedule'
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ env:
+ E2E_REQUIRED_CAPABILITY_MODE: required
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: 1.3.11
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Install browser runtime
+ run: bunx playwright install --with-deps chromium
+
+ - name: Install pinned real clients
+ shell: bash
+ run: |
+ bun install --global @anthropic-ai/claude-code@2.1.195
+ bun install --global opencode-ai@1.17.11
+ echo "E2E_CLAUDE_CODE_BIN=$(command -v claude)" >> "$GITHUB_ENV"
+ echo "E2E_CLAUDE_CODE_REQUIRED=1" >> "$GITHUB_ENV"
+ echo "E2E_CLAUDE_CODE_VERSION=2.1.195" >> "$GITHUB_ENV"
+ command -v opencode
+ echo "E2E_OPENCODE_REQUIRED=1" >> "$GITHUB_ENV"
+
+ - name: Run the production image journeys
+ run: bun run --cwd e2e test:selfhost-docker:hermetic
+
+ - name: Sanitize evidence
+ id: sanitize
+ if: always()
+ run: >-
+ bun e2e/scripts/sanitize-evidence.ts
+ --trusted-project selfhost-docker-hermetic
+
+ - name: Upload evidence
+ if: always() && steps.sanitize.outcome == 'success'
+ uses: actions/upload-artifact@v4
+ with:
+ name: e2e-selfhost-production-${{ github.run_attempt }}
+ path: e2e/runs
+ if-no-files-found: ignore
+ retention-days: 14
+
+ desktop-linux:
+ name: Desktop GUI / Linux packaged app
+ if: github.event_name != 'schedule'
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ env:
+ E2E_REQUIRED_CAPABILITY_MODE: required
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: 1.3.11
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Install Electron runtime dependencies
+ run: bunx playwright install --with-deps chromium
+
+ - name: Run development Electron journeys
+ run: xvfb-run -a bun run --cwd e2e test:desktop
+
+ - name: Run packaged Electron journeys
+ run: xvfb-run -a bun run --cwd e2e test:desktop-packaged
+
+ - name: Sanitize evidence
+ id: sanitize
+ if: always()
+ run: >-
+ bun e2e/scripts/sanitize-evidence.ts
+ --trusted-project desktop
+ --trusted-project desktop-packaged
+
+ - name: Upload evidence
+ if: always() && steps.sanitize.outcome == 'success'
+ uses: actions/upload-artifact@v4
+ with:
+ name: e2e-desktop-linux-${{ github.run_attempt }}
+ path: e2e/runs
+ if-no-files-found: ignore
+ retention-days: 14
+
+ desktop-native-package:
+ name: Native desktop package / ${{ matrix.platform }}
+ if: github.event_name != 'schedule'
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 45
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - os: macos-latest
+ platform: macOS arm64
+ builder-flag: --mac
+ bun-target: bun-darwin-arm64
+ - os: windows-latest
+ platform: Windows x64
+ builder-flag: --win
+ bun-target: bun-windows-x64
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: 1.3.11
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Build embedded web app
+ run: bun run --filter @executor-js/local build
+
+ - name: Build bundled executor
+ env:
+ BUN_TARGET: ${{ matrix.bun-target }}
+ run: bun ./scripts/build-sidecar.ts
+ working-directory: apps/desktop
+
+ - name: Start bundled executor
+ run: bun run test:smoke
+ working-directory: apps/desktop
+
+ - name: Build Electron application
+ run: bunx --bun electron-vite build
+ working-directory: apps/desktop
+
+ - name: Package unsigned native application
+ env:
+ CSC_IDENTITY_AUTO_DISCOVERY: "false"
+ run: bunx --bun electron-builder --config electron-builder.e2e.config.ts ${{ matrix.builder-flag }}
+ working-directory: apps/desktop
+
+ desktop-linux-kvm:
+ name: Desktop VM / Linux KVM packaged app
+ if: >-
+ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') &&
+ vars.E2E_LINUX_KVM_ENABLED == 'true'
+ runs-on: [self-hosted, Linux, X64, executor-e2e-kvm]
+ timeout-minutes: 75
+ env:
+ E2E_DESKTOP_GUI_REQUIRED: "1"
+ E2E_KVM_BASE_IMAGE: ${{ vars.E2E_KVM_BASE_IMAGE }}
+ E2E_KVM_REPOSITORY_SCOPE: repo-${{ github.repository_id }}
+ E2E_KVM_LEDGER_DIR: /var/tmp/executor-kvm-ledgers/repo-${{ github.repository_id }}
+ E2E_KVM_CLEANUP_LEDGER: /var/tmp/executor-kvm-ledgers/repo-${{ github.repository_id }}/run-${{ github.run_id }}-attempt-${{ github.run_attempt }}.json
+ E2E_KVM_STALE_TTL_MS: "21600000"
+ E2E_KVM_WORK_ROOT: ${{ vars.E2E_KVM_WORK_ROOT }}
+ E2E_KVM_RUN_SCOPE: repo-${{ github.repository_id }}-run-${{ github.run_id }}-attempt-${{ github.run_attempt }}-desktop-linux-kvm
+ E2E_REQUIRED_CAPABILITY_MODE: required
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: 1.3.11
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Install pinned Claude Code client
+ shell: bash
+ run: |
+ bun install --global @anthropic-ai/claude-code@2.1.195
+ echo "E2E_CLAUDE_CODE_BIN=$(command -v claude)" >> "$GITHUB_ENV"
+ echo "E2E_CLAUDE_CODE_REQUIRED=1" >> "$GITHUB_ENV"
+ echo "E2E_CLAUDE_CODE_VERSION=2.1.195" >> "$GITHUB_ENV"
+
+ - name: Sweep expired Linux KVM guest scopes
+ run: bun e2e/scripts/cleanup-linux-kvm.ts sweep
+
+ - name: Verify requested Linux KVM capabilities
+ run: bun e2e/scripts/preflight-vm-lane.ts linux-kvm
+
+ - name: Run packaged app in a real KVM desktop guest
+ run: bun run --cwd e2e test:desktop-kvm
+
+ - name: Cleanup exact Linux KVM guest scope
+ if: ${{ always() }}
+ run: bun e2e/scripts/cleanup-linux-kvm.ts "$E2E_KVM_CLEANUP_LEDGER"
+
+ - name: Sanitize evidence
+ id: sanitize
+ if: always()
+ run: bun e2e/scripts/sanitize-evidence.ts --trusted-project desktop-kvm
+
+ - name: Upload evidence
+ if: always() && steps.sanitize.outcome == 'success'
+ uses: actions/upload-artifact@v4
+ with:
+ name: e2e-desktop-linux-kvm-${{ github.run_attempt }}
+ path: e2e/runs
+ if-no-files-found: error
+ retention-days: 14
+
+ live-provider-drift:
+ name: Live provider drift / ${{ matrix.target }}
+ if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
+ continue-on-error: true
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - target: cloud
+ script: test:live:cloud
+ - target: selfhost
+ script: test:live:selfhost
+ - target: cloudflare
+ script: test:live:cloudflare
+ env:
+ E2E_REQUIRED_CAPABILITY_MODE: required
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: 1.3.11
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Install browser runtime
+ run: bunx playwright install --with-deps chromium
+
+ - name: Exercise live providers
+ run: bun run --cwd e2e ${{ matrix.script }}
+
+ - name: Sanitize evidence
+ id: sanitize
+ if: always()
+ run: bun e2e/scripts/sanitize-evidence.ts --trusted-project "${{ matrix.target }}"
+
+ - name: Upload evidence
+ if: always() && steps.sanitize.outcome == 'success'
+ uses: actions/upload-artifact@v4
+ with:
+ name: e2e-live-${{ matrix.target }}-${{ github.run_attempt }}
+ path: e2e/runs
+ if-no-files-found: ignore
+ retention-days: 14
+
+ tart-service-vms:
+ name: Service VM / Tart ${{ matrix.guest }}
+ if: >-
+ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') &&
+ vars.E2E_TART_VM_ENABLED == 'true'
+ runs-on: [self-hosted, macOS, executor-e2e]
+ timeout-minutes: 60
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - guest: macOS
+ vm-os: macos
+ project: cli-macos
+ script: test:cli:macos
+ - guest: Linux
+ vm-os: linux
+ project: cli-linux
+ script: test:cli:linux
+ env:
+ E2E_VM_RUN_SCOPE: repo-${{ github.repository_id }}-run-${{ github.run_id }}-attempt-${{ github.run_attempt }}-tart-${{ matrix.vm-os }}
+ E2E_REQUIRED_CAPABILITY_MODE: required
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: 1.3.11
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Sweep expired managed Tart resources
+ if: matrix.vm-os == 'macos'
+ run: bun e2e/scripts/cleanup-vms.ts tart --sweep-expired --minimum-age-hours 6
+
+ - name: Verify requested Tart capabilities
+ run: bun e2e/scripts/preflight-vm-lane.ts tart ${{ matrix.vm-os }}
+
+ - name: Run real guest reboot journeys
+ run: bun run --cwd e2e ${{ matrix.script }}
+
+ - name: Cleanup exact Tart guest scope
+ if: ${{ always() }}
+ run: bun e2e/scripts/cleanup-vms.ts tart
+
+ - name: Sanitize evidence
+ id: sanitize
+ if: always()
+ run: bun e2e/scripts/sanitize-evidence.ts --trusted-project "${{ matrix.project }}"
+
+ - name: Upload evidence
+ if: always() && steps.sanitize.outcome == 'success'
+ uses: actions/upload-artifact@v4
+ with:
+ name: e2e-tart-${{ matrix.vm-os }}-${{ github.run_attempt }}
+ path: e2e/runs
+ if-no-files-found: ignore
+ retention-days: 14
+
+ windows-service-vm:
+ name: Service VM / Windows EC2
+ if: >-
+ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') &&
+ vars.E2E_WINDOWS_VM_ENABLED == 'true'
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ env:
+ AWS_ACCESS_KEY_ID: ${{ secrets.E2E_AWS_ACCESS_KEY_ID }}
+ AWS_SECRET_ACCESS_KEY: ${{ secrets.E2E_AWS_SECRET_ACCESS_KEY }}
+ AWS_SESSION_TOKEN: ${{ secrets.E2E_AWS_SESSION_TOKEN }}
+ E2E_VM_RUN_SCOPE: repo-${{ github.repository_id }}-run-${{ github.run_id }}-attempt-${{ github.run_attempt }}-ec2-windows
+ E2E_REQUIRED_CAPABILITY_MODE: required
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: 1.3.11
+
+ - name: Install dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Verify requested EC2 capabilities
+ run: bun e2e/scripts/preflight-vm-lane.ts ec2 windows
+
+ - name: Run the real Windows reboot journeys
+ run: bun run --cwd e2e test:cli:windows
+
+ - name: Cleanup exact EC2 guest scope
+ if: ${{ always() }}
+ run: bun e2e/scripts/cleanup-vms.ts ec2
+
+ - name: Sweep expired managed EC2 resources
+ if: ${{ always() && github.event_name == 'schedule' }}
+ run: bun e2e/scripts/cleanup-vms.ts ec2 --sweep-expired --minimum-age-hours 6
+
+ - name: Sanitize evidence
+ id: sanitize
+ if: always()
+ run: bun e2e/scripts/sanitize-evidence.ts --trusted-project cli-windows
+
+ - name: Upload evidence
+ if: always() && steps.sanitize.outcome == 'success'
+ uses: actions/upload-artifact@v4
+ with:
+ name: e2e-windows-service-vm-${{ github.run_attempt }}
+ path: e2e/runs
+ if-no-files-found: ignore
+ retention-days: 14
+
+ aggregate-evidence:
+ name: Evidence publication
+ if: ${{ always() && !cancelled() }}
+ needs:
+ - portable
+ - scheduled-deterministic-gate
+ - selfhost-production
+ - desktop-linux
+ - desktop-native-package
+ - desktop-linux-kvm
+ - live-provider-drift
+ - tart-service-vms
+ - windows-service-vm
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: 1.3.11
+
+ - name: Install evidence viewer dependencies
+ run: bun install --frozen-lockfile
+
+ - name: Download target evidence without flattening
+ uses: actions/download-artifact@v4
+ with:
+ pattern: e2e-*-${{ github.run_attempt }}
+ path: ${{ runner.temp }}/executor-e2e-artifacts
+ merge-multiple: false
+
+ - name: Merge attempt directories without overwrites
+ run: >-
+ bun e2e/scripts/merge-evidence.ts
+ --input-dir "${{ runner.temp }}/executor-e2e-artifacts"
+ --output-dir e2e/runs
+ --run-attempt "${{ github.run_attempt }}"
+ --trusted-runs-output "${{ runner.temp }}/executor-e2e-trusted-runs.json"
+
+ - name: Rebuild the browsable matrix
+ run: bun e2e/scripts/rebuild-viewer.ts
+
+ - name: Sanitize the merged publication bundle
+ run: >-
+ bun e2e/scripts/sanitize-evidence.ts
+ --runs-dir e2e/runs
+ --trusted-lanes "${{ runner.temp }}/executor-e2e-trusted-runs.json"
+
+ - name: Upload the downloadable evidence bundle
+ id: evidence-upload
+ uses: actions/upload-artifact@v4
+ with:
+ name: e2e-evidence-matrix-${{ github.run_attempt }}
+ path: e2e/runs
+ if-no-files-found: error
+ retention-days: 14
+
+ - name: Select safe static publication policy
+ id: publication_policy
+ env:
+ CDN_BASE_URL: ${{ vars.EXECUTOR_PREVIEW_CDN_URL }}
+ IS_FORK_PULL_REQUEST: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ REPOSITORY_ID: ${{ github.repository_id }}
+ RUN_ATTEMPT: ${{ github.run_attempt }}
+ RUN_ID: ${{ github.run_id }}
+ shell: bash
+ run: |
+ if [[ "$IS_FORK_PULL_REQUEST" == "true" ]]; then
+ echo "enabled=false" >> "$GITHUB_OUTPUT"
+ echo "reason=Fork pull requests do not receive R2 publication credentials." >> "$GITHUB_OUTPUT"
+ exit 0
+ fi
+ if [[ -z "$CDN_BASE_URL" ]]; then
+ echo "enabled=false" >> "$GITHUB_OUTPUT"
+ echo "reason=EXECUTOR_PREVIEW_CDN_URL is not configured." >> "$GITHUB_OUTPUT"
+ exit 0
+ fi
+ scope="non-pr"
+ if [[ -n "$PR_NUMBER" ]]; then
+ scope="pr-$PR_NUMBER"
+ fi
+ prefix="e2e/repo-$REPOSITORY_ID/$scope/run-$RUN_ID/attempt-$RUN_ATTEMPT"
+ echo "enabled=true" >> "$GITHUB_OUTPUT"
+ echo "object_prefix=$prefix" >> "$GITHUB_OUTPUT"
+
+ - name: Publish sanitizer-approved evidence to public R2
+ id: static_publication
+ if: steps.publication_policy.outputs.enabled == 'true'
+ env:
+ PUBLIC_BASE_URL: ${{ vars.EXECUTOR_PREVIEW_CDN_URL }}
+ R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
+ R2_ENDPOINT: https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
+ R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
+ run: >-
+ bun e2e/scripts/publish-evidence-r2.ts
+ --runs-dir e2e/runs
+ --bucket executor-previews
+ --prefix "${{ steps.publication_policy.outputs.object_prefix }}"
+ --endpoint "$R2_ENDPOINT"
+ --public-base-url "$PUBLIC_BASE_URL"
+ --source-revision "$GITHUB_SHA"
+ --trusted-lanes "${{ runner.temp }}/executor-e2e-trusted-runs.json"
+
+ - name: Publish hosted scenario and run links
+ if: ${{ always() && steps.static_publication.outcome == 'success' }}
+ env:
+ ARTIFACT_URL: ${{ steps.evidence-upload.outputs.artifact-url }}
+ VIEWER_URL: ${{ steps.static_publication.outputs.viewer_url }}
+ WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ shell: bash
+ run: |
+ bun e2e/scripts/evidence-publication-summary.ts \
+ --manifest e2e/runs/manifest.json \
+ --viewer-url "$VIEWER_URL" >> "$GITHUB_STEP_SUMMARY"
+ {
+ echo
+ echo "- [Download the sanitized evidence bundle]($ARTIFACT_URL)"
+ echo "- [Open this workflow run and its source jobs]($WORKFLOW_URL)"
+ } >> "$GITHUB_STEP_SUMMARY"
+
+ - name: Publish honest artifact-only fallback
+ if: ${{ always() && steps.static_publication.outcome != 'success' }}
+ env:
+ ARTIFACT_URL: ${{ steps.evidence-upload.outputs.artifact-url }}
+ PUBLICATION_ENABLED: ${{ steps.publication_policy.outputs.enabled }}
+ PUBLICATION_REASON: ${{ steps.publication_policy.outputs.reason }}
+ WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ shell: bash
+ run: |
+ reason="$PUBLICATION_REASON"
+ if [[ "$PUBLICATION_ENABLED" == "true" ]]; then
+ reason="Static publication was attempted but did not pass public readback verification."
+ fi
+ {
+ echo "## End-to-end evidence"
+ echo
+ echo "- [Download the sanitized evidence bundle]($ARTIFACT_URL)"
+ echo "- [Open this workflow run and its source jobs]($WORKFLOW_URL)"
+ echo
+ echo "$reason"
+ echo "No hosted viewer URL is available for this attempt. Extract the bundle and serve its directory locally to open \`index.html\`."
+ } >> "$GITHUB_STEP_SUMMARY"
diff --git a/RUNNING.md b/RUNNING.md
index 55c1c6fba..3e7dec6ee 100644
--- a/RUNNING.md
+++ b/RUNNING.md
@@ -41,9 +41,26 @@ working instance of X" — read them before inventing a boot path.
`e2e/AGENTS.md` covers writing scenarios. Operationally:
-- `cd e2e && bun run test` boots dev servers and runs everything;
- `--project cloud|selfhost` narrows. `E2E_CLOUD_URL`/`E2E_SELFHOST_URL`
- attach to an already-running server instead of booting.
+- `cd e2e && bun run test` runs the portable hermetic projects: harness unit
+ tests, client adapters, cloud, selfhost, local, and Cloudflare. The cloud and
+ selfhost projects exclude scenarios whose purpose is to detect drift in a
+ public service.
+- `bun run test:cloud`, `bun run test:selfhost`, and
+ `bun run test:cloudflare` run the corresponding full project, including
+ live-provider checks. Their `:hermetic` variants match the pull-request
+ gates.
+- `bun run test:selfhost-docker:hermetic` runs the same journeys against the
+ production Docker artifact. `bun run test:desktop-packaged` builds and drives
+ the unsigned packaged Electron application and needs a GUI display.
+- `E2E_CLOUD_URL` and `E2E_SELFHOST_URL` attach to an already-running server
+ instead of booting one.
+- Project names describe execution policy, while `E2E_TARGET` describes the
+ deployed product. For example, `cloud` and `cloud-hermetic` both resolve the
+ cloud target and use the same boot recipe.
+- Local exploration allows a scenario to skip when its target does not offer a
+ requested Effect service. CI sets `E2E_REQUIRED_CAPABILITY_MODE=required`, so
+ a missing service promised by the project's matrix fails instead of becoming
+ a green skip. The matrix is `e2e/src/project-matrix.ts`.
- Runs land in `e2e/runs/
Join by domain is available on the Team plan.
- - - + {props.access.status === "loading" ? ( +- No domains yet. Add your company domain so members can join without an invite. + {props.access.canManageOrganization + ? "No domains yet. Add your company domain so members can join without an invite." + : "No domains have been configured for this organization."}
); } @@ -140,6 +156,7 @@ function DomainsSection() {