mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 23:32:12 +03:00
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6 to 7. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
304 lines
13 KiB
YAML
304 lines
13 KiB
YAML
name: Release-Green (continuous)
|
||
|
||
# Solution D — continuous, NON-BLOCKING drift signal for the active release branch.
|
||
#
|
||
# WHY: the full gate (ci.yml) only runs on the release PR (PR → main), so reds
|
||
# accrue silently on release/** and explode — in layers — at release time. This
|
||
# workflow reproduces the release-equivalent validation on the release branch and,
|
||
# when there are HARD failures, opens/updates a single tracking issue.
|
||
#
|
||
# WS5.1 (v3.8.49 quality plan) — two modes:
|
||
# push to release/v* (code paths) → --quick (fast HARD gates, ~5-8min). Catches the
|
||
# captain's direct pushes (sync-back — the one ungated write path) AND the merged
|
||
# COMBINATION right after every PR merge, attributing the offending push range in
|
||
# the issue. Base-red MTTD drops from ≤24h to ≤~15min after the offending push.
|
||
# schedule (3×/day) → full --with-build --full-ci (the deep sweep incl. build+suites).
|
||
#
|
||
# It is NOT a required status check and never touches a contributor PR — it only
|
||
# reports. Ratchet drift (eslint warnings / cognitive-complexity / file-size) is
|
||
# expected mid-cycle and is reported but never raises the alarm on its own; only
|
||
# real defects (typecheck / lint errors / unit / vitest / db-rules / public-creds /
|
||
# package-artifact) flip the issue open.
|
||
|
||
on:
|
||
push:
|
||
branches: ["release/v*", "main"]
|
||
paths:
|
||
- "src/**"
|
||
- "open-sse/**"
|
||
- "bin/**"
|
||
- "electron/**"
|
||
- "scripts/**"
|
||
- "tests/**"
|
||
- "config/**"
|
||
- "package.json"
|
||
- "package-lock.json"
|
||
- "tsconfig*.json"
|
||
schedule:
|
||
- cron: "23 5 * * *" # full sweep — off-peak, distinct from other nightlies
|
||
- cron: "23 12 * * *" # full sweep — midday (WS5.1: 3×/day instead of 1×)
|
||
- cron: "23 18 * * *" # full sweep — evening
|
||
workflow_dispatch:
|
||
inputs:
|
||
branch:
|
||
description: "Release branch to validate (default: highest release/vX.Y.Z)"
|
||
required: false
|
||
type: string
|
||
|
||
permissions:
|
||
contents: read
|
||
issues: write
|
||
|
||
concurrency:
|
||
# push storms during merge campaigns collapse to the newest commit per branch;
|
||
# scheduled full sweeps keep their own single lane.
|
||
group: release-green-${{ github.event_name }}-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
env:
|
||
OMNIROUTE_SKIP_SYSTEM_TRUST: "1"
|
||
|
||
jobs:
|
||
release-green:
|
||
name: Validate active release branch
|
||
# On a push, only run for release/* pushes — a push to main is handled by the
|
||
# main-green job below. Schedule/dispatch always run (they validate the highest release).
|
||
if: ${{ github.event_name != 'push' || startsWith(github.ref_name, 'release/') }}
|
||
# Dynamic runner: with USE_VPS_RUNNER=true (release window / on-demand pre-flight)
|
||
# this runs on the dedicated VPS runner — clean env (no operator OMNIROUTE_API_KEY,
|
||
# no local noauth CLIs => zero machine-specific false positives) and no contention.
|
||
# Nightly cron normally finds the var false (VM off) and falls back to hosted.
|
||
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-release"]')) || 'ubuntu-latest' }}
|
||
env:
|
||
JWT_SECRET: ci-nightly-secret-with-sufficient-length-for-validation
|
||
API_KEY_SECRET: ci-nightly-api-key-secret-long
|
||
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
||
steps:
|
||
- uses: actions/checkout@v7
|
||
with:
|
||
fetch-depth: 0
|
||
persist-credentials: false
|
||
|
||
- name: Resolve active release branch
|
||
id: branch
|
||
env:
|
||
INPUT_BRANCH: ${{ github.event.inputs.branch }}
|
||
EVENT_NAME: ${{ github.event_name }}
|
||
PUSHED_REF: ${{ github.ref_name }}
|
||
run: |
|
||
set -euo pipefail
|
||
if [ -n "${INPUT_BRANCH:-}" ]; then
|
||
TARGET="$INPUT_BRANCH"
|
||
elif [ "$EVENT_NAME" = "push" ]; then
|
||
# validate exactly what was pushed, not the highest branch
|
||
TARGET="$PUSHED_REF"
|
||
else
|
||
# highest release/vX.Y.Z by semver among remote branches
|
||
TARGET=$(git for-each-ref --format='%(refname:short)' 'refs/remotes/origin/release/v*' \
|
||
| sed 's#origin/##' \
|
||
| sort -t/ -k2 -V \
|
||
| tail -1)
|
||
fi
|
||
if [ -z "$TARGET" ]; then echo "No release/v* branch found"; exit 1; fi
|
||
# Strict format guard — reject anything that isn't release/vX.Y.Z (blocks
|
||
# ref/command injection via the workflow_dispatch input).
|
||
if ! printf '%s' "$TARGET" | grep -qE '^release/v[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||
echo "Refusing non-canonical branch name: $TARGET"; exit 1
|
||
fi
|
||
echo "target=$TARGET" >> "$GITHUB_OUTPUT"
|
||
echo "Active release branch: $TARGET"
|
||
|
||
- name: Checkout the release branch
|
||
env:
|
||
TARGET: ${{ steps.branch.outputs.target }}
|
||
run: |
|
||
set -euo pipefail
|
||
git checkout "$TARGET"
|
||
git log -1 --oneline
|
||
|
||
- uses: actions/setup-node@v7
|
||
with:
|
||
node-version: "24"
|
||
cache: npm
|
||
|
||
- uses: ./.github/actions/npm-ci-retry
|
||
|
||
- name: Release-green validation (full)
|
||
id: validate
|
||
env:
|
||
EVENT_NAME: ${{ github.event_name }}
|
||
run: |
|
||
set +e
|
||
# --hermetic: scrub live-test trigger vars (self-hosted runner may carry
|
||
# operator env; hosted ignores the unknown flag before #6300 lands).
|
||
# push → --quick: fast HARD gates only (~5-8min), per-merge signal.
|
||
# schedule/dispatch → --with-build --full-ci: ALSO run every static gate from
|
||
# ci.yml's gate jobs (lint, quality-gate, quality-extended, docs-sync-strict,
|
||
# pr-test-policy) + build + full suites. PRs into release/** only get the
|
||
# fast-gates, so these accrue silently and explode in layers on the release PR
|
||
# (v3.8.46: 11 static base-reds leaked).
|
||
if [ "$EVENT_NAME" = "push" ]; then
|
||
MODE="--quick"
|
||
else
|
||
MODE="--with-build --full-ci"
|
||
fi
|
||
echo "[release-green] mode: $MODE (event: $EVENT_NAME)"
|
||
# shellcheck disable=SC2086 — MODE is an intentional flag list
|
||
node scripts/quality/validate-release-green.mjs --json --hermetic $MODE \
|
||
1> release-green.json 2> release-green.log
|
||
echo "exit=$?" >> "$GITHUB_OUTPUT"
|
||
echo "------- report -------"
|
||
cat release-green.log
|
||
|
||
- name: Open / update tracking issue on HARD failure
|
||
if: steps.validate.outputs.exit != '0'
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
TARGET: ${{ steps.branch.outputs.target }}
|
||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||
EVENT_NAME: ${{ github.event_name }}
|
||
BEFORE_SHA: ${{ github.event.before }}
|
||
AFTER_SHA: ${{ github.event.after }}
|
||
run: |
|
||
set -euo pipefail
|
||
TITLE="🔴 Release branch not green: ${TARGET}"
|
||
{
|
||
echo "The **release-green** validation found HARD failures on \`${TARGET}\`."
|
||
echo "These are real defects that would block the release PR — fix them in the"
|
||
echo "originating PR branch (via co-authorship), not by demanding it from contributors."
|
||
echo ""
|
||
echo "**Run:** ${RUN_URL} (mode: ${EVENT_NAME})"
|
||
# WS5.1 attribution: on push events the offending change IS this push's range
|
||
# (one merge per push in the normal queue), so name it — no bisect needed.
|
||
if [ "$EVENT_NAME" = "push" ] && [ -n "${BEFORE_SHA:-}" ] && \
|
||
git cat-file -e "$BEFORE_SHA" 2>/dev/null; then
|
||
echo ""
|
||
echo "**Offending push range** (\`${BEFORE_SHA:0:9}..${AFTER_SHA:0:9}\`):"
|
||
echo '```'
|
||
git log --no-decorate --oneline "${BEFORE_SHA}..${AFTER_SHA}" | head -20
|
||
echo '```'
|
||
fi
|
||
echo ""
|
||
echo '```'
|
||
sed -n '/──────── verdict ────────/,$p' release-green.log || tail -40 release-green.log
|
||
echo '```'
|
||
echo ""
|
||
echo "_Ratchet drift (eslint warnings / cognitive-complexity / file-size) listed above is expected mid-cycle and is rebaselined at release — it is NOT a contributor concern and did not, on its own, open this issue._"
|
||
} > issue-body.md
|
||
|
||
EXISTING=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open \
|
||
--search "in:title $TITLE" --json number --jq '.[0].number' 2>/dev/null || echo "")
|
||
if [ -n "$EXISTING" ]; then
|
||
gh issue comment "$EXISTING" --repo "$GITHUB_REPOSITORY" --body-file issue-body.md
|
||
echo "Updated existing issue #$EXISTING"
|
||
else
|
||
gh issue create --repo "$GITHUB_REPOSITORY" --title "$TITLE" --body-file issue-body.md
|
||
fi
|
||
|
||
- name: Upload report artifact
|
||
if: always()
|
||
uses: actions/upload-artifact@v7
|
||
with:
|
||
name: release-green-report
|
||
path: |
|
||
release-green.json
|
||
release-green.log
|
||
if-no-files-found: ignore
|
||
|
||
# Companion arm for `main`. Under the parallel-cycle model, main only receives merged
|
||
# work at the release squash — so a gate/infra fix that lands only on release leaves
|
||
# main red the whole cycle, and repo-wide gates (CodeQL alert count, ratchet baselines)
|
||
# turn EVERY PR into main red on a check unrelated to its diff. This detects that and
|
||
# opens a "🔴 main not green" tracking issue. The PREVENTION is the companion-PR reflex
|
||
# (Hard Rule #21 area / _shared/merge-gates.md §8); this is the automated backstop.
|
||
main-green:
|
||
name: Validate main branch
|
||
# On a push, only run for a push to main — a push to release/* is handled by
|
||
# release-green above. Schedule/dispatch always run (they also sweep main).
|
||
if: ${{ github.event_name != 'push' || github.ref_name == 'main' }}
|
||
runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-release"]')) || 'ubuntu-latest' }}
|
||
env:
|
||
JWT_SECRET: ci-nightly-secret-with-sufficient-length-for-validation
|
||
API_KEY_SECRET: ci-nightly-api-key-secret-long
|
||
DISABLE_SQLITE_AUTO_BACKUP: "true"
|
||
steps:
|
||
- uses: actions/checkout@v7
|
||
with:
|
||
ref: main # literal — no injection surface; scheduled runs default to the repo default branch (a release/v*), so pin main explicitly
|
||
fetch-depth: 0
|
||
persist-credentials: false
|
||
|
||
- uses: actions/setup-node@v7
|
||
with:
|
||
node-version: "24"
|
||
cache: npm
|
||
|
||
- uses: ./.github/actions/npm-ci-retry
|
||
|
||
- name: Main-green validation
|
||
id: validate
|
||
env:
|
||
EVENT_NAME: ${{ github.event_name }}
|
||
run: |
|
||
set +e
|
||
# push (a merge into main) → --quick fast HARD gates; schedule/dispatch → full sweep.
|
||
if [ "$EVENT_NAME" = "push" ]; then
|
||
MODE="--quick"
|
||
else
|
||
MODE="--with-build --full-ci"
|
||
fi
|
||
echo "[main-green] mode: $MODE (event: $EVENT_NAME)"
|
||
# shellcheck disable=SC2086 — MODE is an intentional flag list
|
||
node scripts/quality/validate-release-green.mjs --json --hermetic $MODE \
|
||
1> main-green.json 2> main-green.log
|
||
echo "exit=$?" >> "$GITHUB_OUTPUT"
|
||
echo "------- report -------"
|
||
cat main-green.log
|
||
|
||
- name: Open / update tracking issue on HARD failure
|
||
if: steps.validate.outputs.exit != '0'
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||
EVENT_NAME: ${{ github.event_name }}
|
||
run: |
|
||
set -euo pipefail
|
||
TITLE="🔴 main branch not green"
|
||
{
|
||
echo "The **main-green** validation found HARD failures on \`main\`."
|
||
echo ""
|
||
echo "Because \`main\` only receives merged work at the release squash, a gate/infra"
|
||
echo "fix that landed only on the release branch leaves \`main\` broken for the whole"
|
||
echo "cycle — and repo-wide gates (CodeQL alert count, ratchet baselines) then turn"
|
||
echo "**every open PR into main** red on a check unrelated to its diff. The fix is a"
|
||
echo "companion PR \`--base main\` carrying the release-side fix (see"
|
||
echo "\`_shared/merge-gates.md\` §8), NOT chasing each contributor PR."
|
||
echo ""
|
||
echo "**Run:** ${RUN_URL} (mode: ${EVENT_NAME})"
|
||
echo ""
|
||
echo '```'
|
||
sed -n '/──────── verdict ────────/,$p' main-green.log || tail -40 main-green.log
|
||
echo '```'
|
||
echo ""
|
||
echo "_Ratchet drift (eslint warnings / cognitive-complexity / file-size) is expected mid-cycle and did NOT, on its own, open this issue._"
|
||
} > issue-body.md
|
||
|
||
EXISTING=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open \
|
||
--search "in:title $TITLE" --json number --jq '.[0].number' 2>/dev/null || echo "")
|
||
if [ -n "$EXISTING" ]; then
|
||
gh issue comment "$EXISTING" --repo "$GITHUB_REPOSITORY" --body-file issue-body.md
|
||
echo "Updated existing issue #$EXISTING"
|
||
else
|
||
gh issue create --repo "$GITHUB_REPOSITORY" --title "$TITLE" --body-file issue-body.md
|
||
fi
|
||
|
||
- name: Upload report artifact
|
||
if: always()
|
||
uses: actions/upload-artifact@v7
|
||
with:
|
||
name: main-green-report
|
||
path: |
|
||
main-green.json
|
||
main-green.log
|
||
if-no-files-found: ignore
|