142 lines
6.7 KiB
YAML
142 lines
6.7 KiB
YAML
name: Lint pre-flip continue-on-error
|
|
|
|
# Pre-merge gate: blocks PRs that flip `continue-on-error: true → false`
|
|
# on any job in `.gitea/workflows/*.yml` WITHOUT proof that the affected
|
|
# job's recent runs on the target branch (PR base) are actually green.
|
|
#
|
|
# Empirical class: PR #656 / mc#1982. PR #656 (RFC internal#219 Phase 4)
|
|
# flipped 5 platform-build-class jobs `continue-on-error: true → false`
|
|
# on the basis of a "verified green on main via combined-status check".
|
|
# But that "green" was the LIE the prior `continue-on-error: true`
|
|
# produced: Gitea Quirk #10 (internal#342 + dup #287) — a failed step
|
|
# inside a `continue-on-error: true` job rolls up to a `success`
|
|
# job-level status. The precondition the PR claimed to verify was
|
|
# structurally fooled by the bug being flipped.
|
|
#
|
|
# mc#1982 captured the surfaced defects (2 mutually-masked regressions):
|
|
# - Class 1: sqlmock helper drift since 2f36bb9a (24 days old)
|
|
# - Class 2: OFFSEC-001 contract collision since 7d1a189f (1 day old)
|
|
#
|
|
# Codified 04:35Z as hongming-pc2 charter §SOP-N rule (e)
|
|
# "run-log-grep-before-flip" — now structurally enforced here at PR
|
|
# time, ahead of merge.
|
|
#
|
|
# How the gate works:
|
|
# 1. Read every `.gitea/workflows/*.yml` at the PR base SHA AND at
|
|
# the PR head SHA via `git show <sha>:<path>` (no checkout
|
|
# needed).
|
|
# 2. Parse both sides via PyYAML AST (NOT grep — per
|
|
# `feedback_behavior_based_ast_gates`). Walk `jobs.<key>.
|
|
# continue-on-error` on each side. A flip is base=true,
|
|
# head=false.
|
|
# 3. For each flipped job, render the commit-status context as
|
|
# `"{workflow.name} / {job.name or job.key} (push)"` — that's
|
|
# how Gitea Actions emits the per-context status on `main`/
|
|
# `staging` runs.
|
|
# 4. Pull last 5 commits on the PR base branch, fetch combined
|
|
# commit-status per commit, scan for the target context. For
|
|
# each match, fetch the raw run log via the web-UI job-log route
|
|
# `{server_url}/{repo}/actions/runs/{run_id}/jobs/{job_idx}/logs`.
|
|
# The current Actions REST API supplies run metadata and reruns elsewhere;
|
|
# this gate retains the web route because it returns raw job logs directly.
|
|
# 5. Grep each log for `--- FAIL`, `FAIL\s`, `::error::`. If
|
|
# the status is `success` but the log shows any of these,
|
|
# the job was masked. Block the PR with `::error::`.
|
|
#
|
|
# Graceful-degrade contract (per task halt-conditions):
|
|
# - Log fetch 404 (act_runner pruned the log, transient outage):
|
|
# emit `::warning::` "log unavailable" — does NOT block.
|
|
# - Zero recent runs of the flipped job's context on the base
|
|
# branch (newly added workflow): emit `::warning::` "no run
|
|
# history to verify" — allow the flip. Chicken-and-egg
|
|
# exemption.
|
|
# - YAML parse error in one of the workflow files: warn-only,
|
|
# don't block — the YAML lint workflows catch this separately.
|
|
#
|
|
# Cross-links: PR#656, mc#1982, PR#665 (interim re-mask),
|
|
# Quirk #10 (internal#342 + dup #287), hongming-pc2 charter
|
|
# §SOP-N rule (e), feedback_strict_root_only_after_class_a,
|
|
# feedback_no_shared_persona_token_use.
|
|
#
|
|
# Phase contract (RFC internal#219 §1 ladder):
|
|
# - Flipped to `continue-on-error: false` after Researcher live-verified
|
|
# clean runs. The script's own 35 pytest tests pass and recent PR
|
|
# history shows no masked regressions — the gate is now enforcing.
|
|
|
|
on:
|
|
pull_request:
|
|
types: [opened, synchronize, reopened]
|
|
paths:
|
|
- '.gitea/workflows/**'
|
|
- '.gitea/scripts/lint_pre_flip_continue_on_error.py'
|
|
- '.gitea/workflows/lint-pre-flip-continue-on-error.yml'
|
|
|
|
env:
|
|
# Per `feedback_act_runner_github_server_url` — without this,
|
|
# actions/checkout and friends default to github.com → break.
|
|
GITHUB_SERVER_URL: https://git.moleculesai.app
|
|
|
|
permissions:
|
|
contents: read
|
|
# Need read on the API to pull combined commit-status + commit list
|
|
# for the base branch. The raw job-log route uses the same token.
|
|
pull-requests: read
|
|
|
|
concurrency:
|
|
group: lint-pre-flip-coe-${{ github.event.pull_request.head.sha || github.sha }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
scan:
|
|
name: Verify continue-on-error flips have run-log proof
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 8
|
|
# Fail-closed: the lint script is verified clean (35/35 tests pass,
|
|
# Researcher live-check confirmed). Masking removed per mc#1982 close-out.
|
|
continue-on-error: false
|
|
steps:
|
|
- name: Check out PR head (full history for base-SHA access)
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
# `git show <base-sha>:<path>` needs the base SHA's blobs.
|
|
# Shallow=1 would miss it. Same rationale as
|
|
# check-migration-collisions.yml.
|
|
fetch-depth: 0
|
|
- name: Set up Python (PyYAML for AST parsing)
|
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
with:
|
|
python-version: '3.12'
|
|
- name: Install PyYAML
|
|
# Same pin as ci-required-drift.yml — keep dependencies
|
|
# uniform so a Gitea runner cache hits across both jobs.
|
|
run: python -m pip install --quiet 'PyYAML==6.0.2'
|
|
- name: Ensure base ref is reachable locally
|
|
# `actions/checkout@v6 fetch-depth=0` usually pulls the base
|
|
# too, but explicit-fetch is cheap insurance against the
|
|
# form-of-ref differences across Gitea runner versions
|
|
# (mirrors the comment in check-migration-collisions.yml).
|
|
run: |
|
|
git fetch origin "${{ github.event.pull_request.base.ref }}" || true
|
|
- name: Run lint
|
|
env:
|
|
# Auto-injected by Gitea Actions; sufficient scope for
|
|
# combined-status + commit-list + log fetch via web-UI
|
|
# route. NO repo-admin needed (unlike the
|
|
# branch_protections endpoint).
|
|
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
GITEA_HOST: git.moleculesai.app
|
|
REPO: ${{ github.repository }}
|
|
BASE_REF: ${{ github.event.pull_request.base.ref }}
|
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
# Window over recent base-branch commits to find the flipped job's
|
|
# last (push) run. Bumped 5→12: paths-filtered workflows (e.g.
|
|
# lint-continue-on-error-tracking, which runs on push only when
|
|
# `.gitea/workflows/**` changes) may not have run in the last 5
|
|
# main commits, and this gate FAILS CLOSED on checked_commits==0
|
|
# ("cannot verify flip") — a 5-commit window would block a genuine
|
|
# green-on-main flip and wedge the [*] queue. 12 reaches such runs
|
|
# while staying a single un-paginated /commits page.
|
|
RECENT_COMMITS_N: '12'
|
|
run: python3 .gitea/scripts/lint_pre_flip_continue_on_error.py
|