diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index d846abd00..3cd89596a 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -80,6 +80,7 @@ jobs: canvas: ${{ steps.check.outputs.canvas }} python: ${{ steps.check.outputs.python }} scripts: ${{ steps.check.outputs.scripts }} + debug: ${{ steps.check.outputs.debug }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -90,12 +91,23 @@ jobs: PR_BASE_REF: ${{ github.event.pull_request.base.ref }} PUSH_BEFORE: ${{ github.event.before }} run: | + BASE_SHA="${PR_BASE_SHA:-$PUSH_BEFORE}" python3 .gitea/scripts/detect-changes.py \ --profile ci \ --event-name "${{ github.event_name }}" \ --pr-base-sha "$PR_BASE_SHA" \ --base-ref "$PR_BASE_REF" \ - --push-before "${GITHUB_EVENT_BEFORE:-$PUSH_BEFORE}" + --push-before "${GITHUB_EVENT_BEFORE:-$PUSH_BEFORE}" || { + # Script crash / uncaught error: fail open so every CI profile + # runs rather than silently no-oping a potentially-breaking PR. + echo "platform=true" >> "$GITHUB_OUTPUT" + echo "canvas=true" >> "$GITHUB_OUTPUT" + echo "python=true" >> "$GITHUB_OUTPUT" + echo "scripts=true" >> "$GITHUB_OUTPUT" + echo "debug=detect-script-error event=${{ github.event_name }} base=$BASE_SHA" >> "$GITHUB_OUTPUT" + exit 0 + } + echo "debug=profile=ci event=${{ github.event_name }} base=$BASE_SHA" >> "$GITHUB_OUTPUT" # Platform (Go) — Go build/vet/test/lint + coverage gates. The job always # emits the required context, but expensive steps are path-scoped on every @@ -126,7 +138,9 @@ jobs: steps: - if: ${{ needs.changes.outputs.platform != 'true' }} working-directory: . - run: echo "No workspace-server/** changes — Platform (Go) gate satisfied without running Go build/test/lint." + run: | + echo "No workspace-server/** changes — Platform (Go) gate satisfied without running Go build/test/lint." + echo "::notice::detect-changes debug: ${{ needs.changes.outputs.debug }}" - if: ${{ needs.changes.outputs.platform == 'true' }} uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - if: ${{ needs.changes.outputs.platform == 'true' }} @@ -303,7 +317,9 @@ jobs: steps: - if: ${{ needs.changes.outputs.canvas != 'true' }} working-directory: . - run: echo "No canvas/** changes — Canvas (Next.js) gate satisfied without running npm build/test." + run: | + echo "No canvas/** changes — Canvas (Next.js) gate satisfied without running npm build/test." + echo "::notice::detect-changes debug: ${{ needs.changes.outputs.debug }}" - if: ${{ needs.changes.outputs.canvas == 'true' }} uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - if: ${{ needs.changes.outputs.canvas == 'true' }} @@ -351,7 +367,9 @@ jobs: continue-on-error: false steps: - if: ${{ needs.changes.outputs.scripts != 'true' }} - run: echo "No tests/e2e, scripts, or infra/scripts changes — Shellcheck gate satisfied without running script checks." + run: | + echo "No tests/e2e, scripts, or infra/scripts changes — Shellcheck gate satisfied without running script checks." + echo "::notice::detect-changes debug: ${{ needs.changes.outputs.debug }}" - if: ${{ needs.changes.outputs.scripts == 'true' }} uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - if: ${{ needs.changes.outputs.scripts == 'true' }} diff --git a/.gitea/workflows/e2e-api.yml b/.gitea/workflows/e2e-api.yml index bc0f11ea2..99631c0c7 100644 --- a/.gitea/workflows/e2e-api.yml +++ b/.gitea/workflows/e2e-api.yml @@ -128,18 +128,31 @@ jobs: continue-on-error: false outputs: api: ${{ steps.decide.outputs.api }} + debug: ${{ steps.decide.outputs.debug }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - id: decide + env: + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + PR_BASE_REF: ${{ github.event.pull_request.base.ref }} + PUSH_BEFORE: ${{ github.event.before }} run: | + BASE_SHA="${PR_BASE_SHA:-$PUSH_BEFORE}" python3 .gitea/scripts/detect-changes.py \ --profile e2e-api \ --event-name "${{ github.event_name }}" \ - --pr-base-sha "${{ github.event.pull_request.base.sha }}" \ - --base-ref "${{ github.event.pull_request.base.ref }}" \ - --push-before "${GITHUB_EVENT_BEFORE:-${{ github.event.before }}}" + --pr-base-sha "$PR_BASE_SHA" \ + --base-ref "$PR_BASE_REF" \ + --push-before "${GITHUB_EVENT_BEFORE:-$PUSH_BEFORE}" || { + # Script crash / uncaught error: fail open so the E2E gate runs + # rather than silently no-oping a potentially-breaking PR. + echo "api=true" >> "$GITHUB_OUTPUT" + echo "debug=detect-script-error event=${{ github.event_name }} base=$BASE_SHA" >> "$GITHUB_OUTPUT" + exit 0 + } + echo "debug=profile=e2e-api event=${{ github.event_name }} base=$BASE_SHA" >> "$GITHUB_OUTPUT" # ONE job (no job-level `if:`) that always runs and reports under the # required-check name `E2E API Smoke Test`. Real work is gated per-step @@ -179,6 +192,7 @@ jobs: 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)." + echo "::notice::detect-changes debug: ${{ needs.detect-changes.outputs.debug }}" - if: needs.detect-changes.outputs.api == 'true' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - if: needs.detect-changes.outputs.api == 'true' diff --git a/.gitea/workflows/e2e-chat.yml b/.gitea/workflows/e2e-chat.yml index 4e6e887d7..8cbc9ec63 100644 --- a/.gitea/workflows/e2e-chat.yml +++ b/.gitea/workflows/e2e-chat.yml @@ -52,6 +52,7 @@ jobs: continue-on-error: true outputs: chat: ${{ steps.decide.outputs.chat }} + debug: ${{ steps.decide.outputs.debug }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -63,6 +64,7 @@ jobs: run: | if [ "${{ github.event_name }}" = "schedule" ] || [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "chat=true" >> "$GITHUB_OUTPUT" + echo "debug=manual-trigger event=${{ github.event_name }}" >> "$GITHUB_OUTPUT" exit 0 fi BASE="${GITHUB_BASE_REF:-${{ github.event.before }}}" @@ -71,6 +73,7 @@ jobs: fi if [ -z "$BASE" ] || echo "$BASE" | grep -qE '^0+$'; then echo "chat=true" >> "$GITHUB_OUTPUT" + echo "debug=new-branch-fallback base=$BASE" >> "$GITHUB_OUTPUT" exit 0 fi if ! git cat-file -e "$BASE" 2>/dev/null; then @@ -78,15 +81,19 @@ jobs: fi if ! git cat-file -e "$BASE" 2>/dev/null; then echo "chat=true" >> "$GITHUB_OUTPUT" + echo "debug=base-missing-fallback base=$BASE" >> "$GITHUB_OUTPUT" exit 0 fi CHANGED=$(git diff --name-only "$BASE" HEAD) + CHANGED_FLAT=$(echo "$CHANGED" | tr '\n' ',') if ! echo "$CHANGED" | grep -qE '^(canvas/|workspace-server/|\.gitea/workflows/e2e-chat\.yml$)'; then echo "chat=false" >> "$GITHUB_OUTPUT" + echo "debug=no-matching-paths base=$BASE changed=$CHANGED_FLAT" >> "$GITHUB_OUTPUT" exit 0 fi if [ "${{ github.event_name }}" != "pull_request" ]; then echo "chat=true" >> "$GITHUB_OUTPUT" + echo "debug=non-pr-run base=$BASE changed=$CHANGED_FLAT" >> "$GITHUB_OUTPUT" exit 0 fi @@ -95,13 +102,23 @@ jobs: printf 'header = "Authorization: token %s"\n' "$GITEA_TOKEN" > "$authfile" labels=$(curl -fsS -K "$authfile" \ "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels" \ - | python3 -c 'import json,sys; print("\n".join(label.get("name","") for label in json.load(sys.stdin)))') + | python3 -c 'import json,sys; print("\n".join(label.get("name","") for label in json.load(sys.stdin)))') || { + # Labels API unavailable: fail open so the E2E gate runs rather + # than silently no-oping a potentially-breaking PR. + rm -f "$authfile" + echo "chat=true" >> "$GITHUB_OUTPUT" + echo "debug=labels-api-unavailable pr=${{ github.event.pull_request.number }} base=$BASE changed=$CHANGED_FLAT" >> "$GITHUB_OUTPUT" + exit 0 + } rm -f "$authfile" + LABELS_FLAT=$(echo "$labels" | tr '\n' ',') if printf '%s\n' "$labels" | grep -qx "$QUEUE_LABEL"; then echo "chat=true" >> "$GITHUB_OUTPUT" + echo "debug=merge-queue-labeled base=$BASE changed=$CHANGED_FLAT" >> "$GITHUB_OUTPUT" else echo "PR is not in merge-queue; skipping heavy E2E Chat for normal PR path." echo "chat=false" >> "$GITHUB_OUTPUT" + echo "debug=no-merge-queue-label base=$BASE changed=$CHANGED_FLAT labels=$LABELS_FLAT" >> "$GITHUB_OUTPUT" fi # bp-required: pending #1142 — new E2E check; add to branch protection after 3 green runs. @@ -146,6 +163,7 @@ jobs: run: | echo "No canvas / workspace-server / workflow changes — E2E Chat gate satisfied without running tests." echo "::notice::E2E Chat no-op pass (paths filter excluded this commit)." + echo "::notice::detect-changes debug: ${{ needs.detect-changes.outputs.debug }}" - if: needs.detect-changes.outputs.chat == 'true' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.gitea/workflows/e2e-peer-visibility.yml b/.gitea/workflows/e2e-peer-visibility.yml index 18e4c84c2..4cf7664a0 100644 --- a/.gitea/workflows/e2e-peer-visibility.yml +++ b/.gitea/workflows/e2e-peer-visibility.yml @@ -125,6 +125,7 @@ jobs: continue-on-error: false outputs: peervis: ${{ steps.filter.outputs.peervis }} + debug: ${{ steps.filter.outputs.debug }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -137,12 +138,20 @@ jobs: PR_BASE_REF: ${{ github.event.pull_request.base.ref }} PUSH_BEFORE: ${{ github.event.before }} run: | + BASE_SHA="${PR_BASE_SHA:-$PUSH_BEFORE}" python3 .gitea/scripts/detect-changes.py \ --profile peer-visibility \ --event-name "${{ github.event_name }}" \ --pr-base-sha "$PR_BASE_SHA" \ --base-ref "$PR_BASE_REF" \ - --push-before "${GITHUB_EVENT_BEFORE:-$PUSH_BEFORE}" + --push-before "${GITHUB_EVENT_BEFORE:-$PUSH_BEFORE}" || { + # Script crash / uncaught error: fail open so the E2E gate runs + # rather than silently no-oping a potentially-breaking PR. + echo "peervis=true" >> "$GITHUB_OUTPUT" + echo "debug=detect-script-error event=${{ github.event_name }} base=$BASE_SHA" >> "$GITHUB_OUTPUT" + exit 0 + } + echo "debug=profile=peer-visibility event=${{ github.event_name }} base=$BASE_SHA" >> "$GITHUB_OUTPUT" # THE required-check emitter. ONE always-running job (no job-level `if:`) # named `E2E Peer Visibility`, so it posts EXACTLY ONE check run under the @@ -208,6 +217,7 @@ jobs: run: | echo "No peer-visibility-relevant changes — E2E Peer Visibility gate satisfied without running the staging E2E." echo "::notice::E2E Peer Visibility no-op pass (detect-changes profile peer-visibility excluded this push)." + echo "::notice::detect-changes debug: ${{ needs.changes.outputs.debug }}" # --- push/dispatch/cron + relevant change: the REAL staging E2E ------ - name: Verify admin token present diff --git a/.gitea/workflows/e2e-staging-canvas.yml b/.gitea/workflows/e2e-staging-canvas.yml index c5d46ddfd..eff410503 100644 --- a/.gitea/workflows/e2e-staging-canvas.yml +++ b/.gitea/workflows/e2e-staging-canvas.yml @@ -96,6 +96,7 @@ jobs: continue-on-error: true outputs: canvas: ${{ steps.decide.outputs.canvas }} + debug: ${{ steps.decide.outputs.debug }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -109,6 +110,7 @@ jobs: run: | if [ "${{ github.event_name }}" = "schedule" ] || [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "canvas=true" >> "$GITHUB_OUTPUT" + echo "debug=manual-trigger event=${{ github.event_name }}" >> "$GITHUB_OUTPUT" exit 0 fi BASE="${GITHUB_BASE_REF:-${{ github.event.before }}}" @@ -117,6 +119,7 @@ jobs: fi if [ -z "$BASE" ] || echo "$BASE" | grep -qE '^0+$'; then echo "canvas=true" >> "$GITHUB_OUTPUT" + echo "debug=new-branch-fallback base=$BASE" >> "$GITHUB_OUTPUT" exit 0 fi if ! git cat-file -e "$BASE" 2>/dev/null; then @@ -124,15 +127,19 @@ jobs: fi if ! git cat-file -e "$BASE" 2>/dev/null; then echo "canvas=true" >> "$GITHUB_OUTPUT" + echo "debug=base-missing-fallback base=$BASE" >> "$GITHUB_OUTPUT" exit 0 fi CHANGED=$(git diff --name-only "$BASE" HEAD) + CHANGED_FLAT=$(echo "$CHANGED" | tr '\n' ',') if ! echo "$CHANGED" | grep -qE '^(canvas/|\.gitea/workflows/e2e-staging-canvas\.yml$)'; then echo "canvas=false" >> "$GITHUB_OUTPUT" + echo "debug=no-matching-paths base=$BASE changed=$CHANGED_FLAT" >> "$GITHUB_OUTPUT" exit 0 fi if [ "${{ github.event_name }}" != "pull_request" ]; then echo "canvas=true" >> "$GITHUB_OUTPUT" + echo "debug=non-pr-run base=$BASE changed=$CHANGED_FLAT" >> "$GITHUB_OUTPUT" exit 0 fi @@ -141,13 +148,23 @@ jobs: printf 'header = "Authorization: token %s"\n' "$GITEA_TOKEN" > "$authfile" labels=$(curl -fsS -K "$authfile" \ "${{ github.server_url }}/api/v1/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels" \ - | python3 -c 'import json,sys; print("\n".join(label.get("name","") for label in json.load(sys.stdin)))') + | python3 -c 'import json,sys; print("\n".join(label.get("name","") for label in json.load(sys.stdin)))') || { + # Labels API unavailable: fail open so the E2E gate runs rather + # than silently no-oping a potentially-breaking PR. + rm -f "$authfile" + echo "canvas=true" >> "$GITHUB_OUTPUT" + echo "debug=labels-api-unavailable pr=${{ github.event.pull_request.number }} base=$BASE changed=$CHANGED_FLAT" >> "$GITHUB_OUTPUT" + exit 0 + } rm -f "$authfile" + LABELS_FLAT=$(echo "$labels" | tr '\n' ',') if printf '%s\n' "$labels" | grep -qx "$QUEUE_LABEL"; then echo "canvas=true" >> "$GITHUB_OUTPUT" + echo "debug=merge-queue-labeled base=$BASE changed=$CHANGED_FLAT" >> "$GITHUB_OUTPUT" else echo "PR is not in merge-queue; skipping heavy E2E Staging Canvas for normal PR path." echo "canvas=false" >> "$GITHUB_OUTPUT" + echo "debug=no-merge-queue-label base=$BASE changed=$CHANGED_FLAT labels=$LABELS_FLAT" >> "$GITHUB_OUTPUT" fi # ONE job (no job-level `if:`) that always runs and reports under the @@ -184,6 +201,7 @@ jobs: 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)." + echo "::notice::detect-changes debug: ${{ needs.detect-changes.outputs.debug }}" - if: needs.detect-changes.outputs.canvas == 'true' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.gitea/workflows/handlers-postgres-integration.yml b/.gitea/workflows/handlers-postgres-integration.yml index 93a9fd91f..389bc005a 100644 --- a/.gitea/workflows/handlers-postgres-integration.yml +++ b/.gitea/workflows/handlers-postgres-integration.yml @@ -93,6 +93,7 @@ jobs: continue-on-error: false outputs: handlers: ${{ steps.filter.outputs.handlers }} + debug: ${{ steps.filter.outputs.debug }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -102,13 +103,25 @@ jobs: # not present in the shallow checkout. fetch-depth: 2 - id: filter + env: + PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} + PR_BASE_REF: ${{ github.event.pull_request.base.ref }} + PUSH_BEFORE: ${{ github.event.before }} run: | + BASE_SHA="${PR_BASE_SHA:-$PUSH_BEFORE}" python3 .gitea/scripts/detect-changes.py \ --profile handlers-postgres \ --event-name "${{ github.event_name }}" \ - --pr-base-sha "${{ github.event.pull_request.base.sha }}" \ - --base-ref "${{ github.event.pull_request.base.ref }}" \ - --push-before "${GITHUB_EVENT_BEFORE:-}" + --pr-base-sha "$PR_BASE_SHA" \ + --base-ref "$PR_BASE_REF" \ + --push-before "${GITHUB_EVENT_BEFORE:-}" || { + # Script crash / uncaught error: fail open so the integration gate + # runs rather than silently no-oping a potentially-breaking PR. + echo "handlers=true" >> "$GITHUB_OUTPUT" + echo "debug=detect-script-error event=${{ github.event_name }} base=$BASE_SHA" >> "$GITHUB_OUTPUT" + exit 0 + } + echo "debug=profile=handlers-postgres event=${{ github.event_name }} base=$BASE_SHA" >> "$GITHUB_OUTPUT" # ONE job (no job-level `if:`) that always runs and reports under the # required-check name `Handlers Postgres Integration`. Real work is gated @@ -148,6 +161,7 @@ jobs: run: | echo "No handlers/migrations changes — Handlers Postgres Integration gate satisfied without running tests." echo "::notice::Handlers Postgres Integration no-op pass (paths filter excluded this commit)." + echo "::notice::detect-changes debug: ${{ needs.detect-changes.outputs.debug }}" - if: needs.detect-changes.outputs.handlers == 'true' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.gitea/workflows/harness-replays.yml b/.gitea/workflows/harness-replays.yml index 095d8a436..728f1118c 100644 --- a/.gitea/workflows/harness-replays.yml +++ b/.gitea/workflows/harness-replays.yml @@ -74,6 +74,7 @@ jobs: continue-on-error: true outputs: run: ${{ steps.decide.outputs.run }} + debug: ${{ steps.decide.outputs.debug }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: @@ -120,12 +121,13 @@ jobs: | bash .gitea/scripts/push-commits-diff-files.py \ > .push-diff-files.txt 2>/dev/null || true DIFF_FILES=$(cat .push-diff-files.txt 2>/dev/null || true) + DIFF_FILES_FLAT=$(echo "$DIFF_FILES" | tr '\n' ',') if [ -n "$DIFF_FILES" ] && echo "$DIFF_FILES" | grep -qE '^workspace-server/|^canvas/|^tests/harness/|^.gitea/workflows/harness-replays\.yml$'; then echo "run=true" >> "$GITHUB_OUTPUT" else echo "run=false" >> "$GITHUB_OUTPUT" fi - echo "debug=push-files=$DIFF_FILES" >> "$GITHUB_OUTPUT" + echo "debug=push-files=$DIFF_FILES_FLAT" >> "$GITHUB_OUTPUT" exit 0 else # New branch or github.event.before unavailable — run everything. @@ -148,8 +150,9 @@ jobs: exit 0 } DIFF_FILES=$(echo "$RESP" | bash .gitea/scripts/compare-api-diff-files.py 2>/dev/null || true) + DIFF_FILES_FLAT=$(echo "$DIFF_FILES" | tr '\n' ',') - echo "debug=diff-base=$BASE diff-files=$DIFF_FILES" >> "$GITHUB_OUTPUT" + echo "debug=diff-base=$BASE diff-files=$DIFF_FILES_FLAT" >> "$GITHUB_OUTPUT" if echo "$DIFF_FILES" | grep -qE '^workspace-server/|^canvas/|^tests/harness/|^.gitea/workflows/harness-replays\.yml$'; then echo "run=true" >> "$GITHUB_OUTPUT"