name: lint-bp-context-emit-match

# Tier 2f scheduled lint (per mc#774) — detects drift between
# `branch_protections/<branch>.status_check_contexts` and the set of
# contexts emitted by `.gitea/workflows/*.yml`.
#
# Rule
# ----
# For each protected branch context (Source A — BP), there must exist
# at least one emitting workflow + job pair (Source B — workflow YAML
# + on:-event mapping) whose runtime status-name maps to it. The
# inverse direction (emitter without BP context) is informational
# only — Tier 2g handles that at PR-time.
#
# Why this exists
# ---------------
# A BP-required context with no emitter blocks merges forever — Gitea
# 1.22.6 treats absent-as-`pending`, NOT absent-as-`skipped`. The
# phantom-required-check class previously surfaced as
# `feedback_phantom_required_check_after_gitea_migration` (a port
# kept the GitHub context name after rename to Gitea, but no
# workflow emitted under the new name).
#
# This lint catches the same class structurally + a forward case:
# workflow renamed/deleted while still in BP.
#
# Scope
# -----
# Scheduled daily. We DON'T run on `pull_request` because (a) the
# emitter side moves with PR diffs (transitional state false-flags)
# and (b) Tier 2g handles emitter-side drift at PR-time.
#
# Cross-repo
# ----------
# Today this runs only on molecule-core/main. Per internal#349
# (cross-repo BP sweep) Class-D repos will get the same lint after
# their BP rollouts.
#
# Auth
# ----
# `GET /repos/.../branch_protections/{branch}` requires repo-admin
# role on Gitea 1.22.6. We use DRIFT_BOT_TOKEN (same persona as
# ci-required-drift.yml — `internal#329` provisioning trail).
# Graceful-degrade per Tier 2a contract: 403/404 → exit 0 with
# ::error::.
#
# Idempotency
# -----------
# The drift issue is filed with title prefix
# `[ci-bp-drift] {repo}/{branch}: BP→emitter mismatch`. The script
# searches OPEN issues for an exact title-prefix match and PATCHes
# the existing issue (if any) instead of POSTing a duplicate.
# Mirrors `ci-required-drift.py`'s contract.
#
# Phase contract (RFC internal#219 §1 ladder)
# -------------------------------------------
# Lands at `continue-on-error: true` (Phase 3). After 7 days of clean
# scheduled runs on `main`, flip to `false` so a scheduled failure
# becomes a hard CI signal.
#
# Cross-links
# -----------
# - mc#774 (the RFC that specs this lint)
# - internal#349 (cross-repo BP sweep)
# - feedback_phantom_required_check_after_gitea_migration
# - feedback_tier_label_ids_are_per_repo
# - ci-required-drift.yml (F2 detector, narrower-scope sibling)

on:
  schedule:
    # Daily at 03:31 UTC — off-peak, prime-staggered from other
    # scheduled jobs (ci-required-drift :00 hourly, lint-coe-tracking
    # 13:11). At 03:31 the CI fleet is quietest in EMEA hours.
    - cron: '31 3 * * *'
  workflow_dispatch:
  # No `push` / `pull_request` here — Tier 2g owns PR-time drift.

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

permissions:
  contents: read
  issues: write  # needed to file/edit the drift issue

concurrency:
  group: lint-bp-context-emit-match-${{ github.ref }}
  cancel-in-progress: true

jobs:
  lint:
    name: lint-bp-context-emit-match
    runs-on: ubuntu-latest
    timeout-minutes: 5
    # Phase 3 (RFC #219 §1): surface drift without blocking. After 7
    # clean scheduled runs on main, flip to false so a scheduled
    # failure is a hard CI signal.
    continue-on-error: true  # mc#774 Phase 3 — flip to false after 7 clean main runs
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2
      - 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: Run lint-bp-context-emit-match
        env:
          # DRIFT_BOT_TOKEN — repo-admin on this repo (internal#329
          # provisioning trail). Required for branch_protections read.
          GITEA_TOKEN: ${{ secrets.DRIFT_BOT_TOKEN }}
          GITEA_HOST: git.moleculesai.app
          REPO: ${{ github.repository }}
          BRANCH: main
          WORKFLOWS_DIR: .gitea/workflows
          DRIFT_LABEL: ci-bp-drift
          GITHUB_RUN_URL: https://git.moleculesai.app/${{ github.repository }}/actions/runs/${{ github.run_id }}
        run: python3 .gitea/scripts/lint_bp_context_emit_match.py
      - name: Run lint-bp-context-emit-match unit tests
        run: |
          python -m pip install --quiet pytest
          python3 -m pytest tests/test_lint_bp_context_emit_match.py -v
