name: lint-required-context-exists-in-bp

# Tier 2g hard-gate lint (per mc#774) — diff-based PR-time
# check. When a PR adds a NEW commit-status emission (workflow YAML
# `name:` + job `name:`-or-key + on:-event), the workflow file must
# carry one of three directives adjacent to the new job:
#
#   - `# bp-required: yes`           — and BP must list the context
#   - `# bp-required: pending #NNN`  — acknowledged asymmetry + tracker
#   - `# bp-exempt: <reason>`        — informational job, not a gate
#
# Default (no directive on a new emitter) = FAIL.
#
# Why this exists
# ---------------
# PR#656 added `CI / all-required (pull_request)` as a sentinel
# context that workflows emit, but BP did NOT list it. When
# platform-build failed, all-required failed, but BP let the PR
# merge anyway → cascade to mc#774. With this lint, PR#656 would
# have been blocked until either the BP PATCH ran alongside OR
# the author added a `bp-required: pending` directive.
#
# Tier 2g vs Tier 2f
# ------------------
# Tier 2g runs at PR-time (diff-based) and BLOCKS the merge.
# Tier 2f runs daily (scheduled) and FILES a drift issue. They
# share the workflow-context enumeration helpers
# (`_event_map`, `workflow_contexts`, `_job_display`) but the
# semantics are intentionally distinct so they're separate scripts.
# Co-design is documented in mc#774.
#
# Directive comment lives in the workflow file (NOT PR body)
# ----------------------------------------------------------
# A PR-body claim of "BP exempt" evaporates on merge — the
# asymmetry returns to undetected state and Tier 2f's daily
# scheduled audit can't see it. The directive must live with the
# emitter so both PR-time (Tier 2g) and post-merge (Tier 2f)
# readers consume the same source.
#
# Phase contract (RFC internal#219 §1 ladder)
# -------------------------------------------
# Lands at `continue-on-error: true` (Phase 3 — surface the
# pattern without blocking PRs while the directive convention
# beds in). After 7 days of clean runs on `main` with no false
# positives, follow-up flips to `false`. Tracking: mc#774.
#
# Cross-links
# -----------
# - mc#774 (the RFC that specs this lint)
# - PR#656 (the empirical case)
# - mc#774 (the surfaced cascade)
# - feedback_phantom_required_check_after_gitea_migration (Tier 2f cousin)
# - feedback_behavior_based_ast_gates
#
# Auth: DRIFT_BOT_TOKEN (repo-admin for branch_protections read).

on:
  pull_request:
    types: [opened, synchronize, reopened]
    paths:
      - '.gitea/workflows/**'
      - '.gitea/scripts/lint_required_context_exists_in_bp.py'
      - '.gitea/workflows/lint-required-context-exists-in-bp.yml'
      - 'tests/test_lint_required_context_exists_in_bp.py'

env:
  GITHUB_SERVER_URL: https://git.moleculesai.app

permissions:
  contents: read

concurrency:
  group: lint-required-context-exists-in-bp-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  # bp-exempt: this lint is a PR-time advisory and is not intended to
  # be a required gate on main. The directive eat-our-own-dogfood
  # confirms the convention works on the lint that defines it.
  lint:
    name: lint-required-context-exists-in-bp
    runs-on: ubuntu-latest
    timeout-minutes: 5
    # Phase 3 (RFC #219 §1): surface the pattern without blocking PRs
    # while the directive convention beds in. Follow-up flip to false
    # after 7 clean days on main. mc#774.
    continue-on-error: true  # mc#774 Phase 3 — flip to false after 7 clean main runs
    steps:
      - name: Check out PR head with full history (need base SHA blobs)
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2
        with:
          # `git show <base-sha>:<path>` needs the base SHA's blobs.
          # Same rationale as PR#673 and check-migration-collisions.yml.
          fetch-depth: 0
      - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065  # v5.6.0
        with:
          python-version: '3.12'
      - name: Install PyYAML
        run: python -m pip install --quiet 'PyYAML==6.0.2'
      - name: Ensure base ref is reachable locally
        # Cheap insurance against runner-version drift.
        run: |
          git fetch origin "${{ github.event.pull_request.base.ref }}" || true
      - name: Run lint-required-context-exists-in-bp
        env:
          # DRIFT_BOT_TOKEN — repo-admin (needed for branch_protections).
          GITEA_TOKEN: ${{ secrets.DRIFT_BOT_TOKEN }}
          GITEA_HOST: git.moleculesai.app
          REPO: ${{ github.repository }}
          BRANCH: main
          BASE_SHA: ${{ github.event.pull_request.base.sha }}
          HEAD_SHA: ${{ github.event.pull_request.head.sha }}
          WORKFLOWS_DIR: .gitea/workflows
        run: python3 .gitea/scripts/lint_required_context_exists_in_bp.py
      - name: Run lint-required-context-exists-in-bp unit tests
        run: |
          python -m pip install --quiet pytest
          python3 -m pytest tests/test_lint_required_context_exists_in_bp.py -v
