Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions .github/workflows/auto-promote-staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,30 @@ jobs:
run: |
set -euo pipefail

# Required gate workflow names. Must match the `name:` field
# in the respective .github/workflows/*.yml files.
# Required gate workflow files. Use file paths (relative to
# .github/workflows/) rather than display names because:
#
# 1. `gh run list --workflow=<name>` is ambiguous when two
# workflows have the same `name:` — observed 2026-04-28
# with "CodeQL" matching both `codeql.yml` (explicit) and
# GitHub's UI-configured Code-quality default setup
# (internal "codeql"). gh CLI returns "could not resolve
# to a unique workflow" → empty result → gate evaluated
# as missing/none → auto-promote dead-locked despite all
# checks actually passing.
#
# 2. File paths are the unique identifier for workflows;
# `name:` is just a display string and can collide.
#
# When adding/removing a gate, update this list AND the
# branch-protection required-checks list (which uses check-run
# display names, not workflow names; the two are decoupled and
# should be kept in sync manually).
GATES=(
"CI"
"E2E Staging Canvas (Playwright)"
"E2E API Smoke Test"
"CodeQL"
"ci.yml"
"e2e-staging-canvas.yml"
"e2e-api.yml"
"codeql.yml"
)

echo "head_sha=${HEAD_SHA}" >> "$GITHUB_OUTPUT"
Expand Down
62 changes: 54 additions & 8 deletions .github/workflows/e2e-api.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,73 @@
name: E2E API Smoke Test
# Extracted from ci.yml so workflow-level concurrency can protect this job
# from run-level cancellation (issue #458).
#
# Trigger model (changed 2026-04-28 — see auto-promote gap below):
#
# This workflow always FIRES on push/pull_request to staging+main, but
# only does real work when paths under `workspace-server/`,
# `tests/e2e/`, or this workflow file changed. The detect-changes job
# uses dorny/paths-filter to decide; the e2e-api job runs only if
# changes match. Otherwise the no-op job emits success so the workflow
# always produces a `completed/success` run record.
#
# Why: auto-promote-staging.yml's gate-check (line 99) treats "workflow
# didn't run" as failure, which dead-locked any platform-only or
# test-only push to staging that didn't touch workspace-server paths.
# Dropping the path filter on the trigger and gating real work
# internally guarantees the workflow always emits a result that the
# auto-promote chain can read. Same pattern applied to
# e2e-staging-canvas.yml in the same PR.

on:
push:
branches: [main, staging]
paths:
- 'workspace-server/**'
- 'tests/e2e/**'
- '.github/workflows/e2e-api.yml'
pull_request:
branches: [main, staging]
paths:
- 'workspace-server/**'
- 'tests/e2e/**'
- '.github/workflows/e2e-api.yml'
workflow_dispatch:

concurrency:
group: e2e-api-${{ github.ref }}
cancel-in-progress: false

jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
api: ${{ steps.decide.outputs.api }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
api:
- 'workspace-server/**'
- 'tests/e2e/**'
- '.github/workflows/e2e-api.yml'
- id: decide
# Always run real work for manual dispatch — no diff context to
# filter against and ops dispatching this expects the suite to
# actually exercise the platform.
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "api=true" >> "$GITHUB_OUTPUT"
else
echo "api=${{ steps.filter.outputs.api }}" >> "$GITHUB_OUTPUT"
fi

no-op:
needs: detect-changes
if: needs.detect-changes.outputs.api != 'true'
runs-on: ubuntu-latest
steps:
- run: |
echo "No workspace-server / tests/e2e / workflow changes — E2E API gate satisfied without running tests."
echo "::notice::E2E API Smoke Test no-op pass (paths filter excluded this commit)."

e2e-api:
needs: detect-changes
if: needs.detect-changes.outputs.api == 'true'
name: E2E API Smoke Test
runs-on: ubuntu-latest
timeout-minutes: 15
Expand Down
53 changes: 47 additions & 6 deletions .github/workflows/e2e-staging-canvas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,23 @@ name: E2E Staging Canvas (Playwright)
# workflow — mirrors what PR #1891 does for e2e-api.yml.

on:
# Trigger model (changed 2026-04-28 — see auto-promote gap below):
#
# Always fires on push/pull_request; only does real work when canvas/
# or this workflow file changed. The detect-changes job uses
# dorny/paths-filter to decide; the playwright job runs only if
# changes match. Otherwise no-op emits success so the workflow always
# produces a `completed/success` run record.
#
# Why: auto-promote-staging.yml's gate-check (line 99) treats
# "workflow didn't run" as failure, which dead-locked platform-only
# pushes to staging. Dropping the trigger path filter and gating real
# work internally guarantees a result the auto-promote chain can
# read. Same pattern applied to e2e-api.yml in the same PR.
push:
branches: [main, staging]
paths:
- 'canvas/**'
- '.github/workflows/e2e-staging-canvas.yml'
pull_request:
branches: [main, staging]
paths:
- 'canvas/**'
- '.github/workflows/e2e-staging-canvas.yml'
workflow_dispatch:
schedule:
# Weekly on Sunday 08:00 UTC — catches Chrome / Playwright / Next.js
Expand All @@ -34,7 +41,41 @@ concurrency:
cancel-in-progress: false

jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
canvas: ${{ steps.decide.outputs.canvas }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
canvas:
- 'canvas/**'
- '.github/workflows/e2e-staging-canvas.yml'
- id: decide
# Always run real tests for manual dispatch and the weekly cron —
# both exist precisely to exercise the suite, regardless of diff.
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ] || [ "${{ github.event_name }}" = "schedule" ]; then
echo "canvas=true" >> "$GITHUB_OUTPUT"
else
echo "canvas=${{ steps.filter.outputs.canvas }}" >> "$GITHUB_OUTPUT"
fi

no-op:
needs: detect-changes
if: needs.detect-changes.outputs.canvas != 'true'
runs-on: ubuntu-latest
steps:
- run: |
echo "No canvas / workflow changes — E2E Staging Canvas gate satisfied without running tests."
echo "::notice::E2E Staging Canvas no-op pass (paths filter excluded this commit)."

playwright:
needs: detect-changes
if: needs.detect-changes.outputs.canvas == 'true'
name: Canvas tabs E2E
runs-on: ubuntu-latest
timeout-minutes: 40
Expand Down
Loading