name: lint-continue-on-error-tracking

# Tier 2e hard-gate lint (per mc#774) — every
# `continue-on-error: true` in `.gitea/workflows/*.yml` must carry a
# `# mc#NNNN` or `# internal#NNNN` tracker comment within 2 lines,
# the referenced issue must be OPEN, and ≤14 days old.
#
# Why this exists
# ---------------
# `continue-on-error: true` on `platform-build` had been hiding
# mc#774-class regressions for ~3 weeks before #656 surfaced them on
# 2026-05-12. A 14-day cap on tracker age forces a review cycle and
# surfaces mask-drift within at most 14 days of the original defect.
# Each `continue-on-error: true` gets a paper trail — close or renew.
#
# How the gate works
# ------------------
# 1. Walk `.gitea/workflows/*.yml` via PyYAML's line-tracking loader
#    (per `feedback_behavior_based_ast_gates`) and find every job
#    whose `continue-on-error` evaluates truthy (`true` or string
#    `"true"` — Gitea's evaluator coerces strings).
# 2. For each, scan ±2 lines of the directive's source line for a
#    `# mc#NNNN` or `# internal#NNNN` comment. Inline-trailing
#    comments on the directive line count.
# 3. For each tracker reference, GET the issue from the Gitea API.
#    Validate: exists, `state == open`, `created_at` ≤ MAX_AGE_DAYS.
# 4. Aggregate ALL violations (not short-circuit) and exit 1 if any.
#
# Triggers
# --------
# Runs on PR events (paths-filter on `.gitea/workflows/**`) AND on
# a daily schedule. PR runs catch the violation at introduction time.
# Schedule runs catch the AGE-EXPIRY class: a tracker that was ≤14d
# old when the PR landed but is now 20d old, with the underlying
# defect still unfixed. Per `feedback_chained_defects_in_never_tested_workflows`,
# scheduled drift detection is the second half of the gate.
#
# Phase contract (RFC internal#219 §1 ladder)
# -------------------------------------------
# Lands at `continue-on-error: true` (Phase 3 — surface broken shapes
# without blocking). The pre-existing `continue-on-error: true`
# directives on `main` will all violate this lint at first
# (intentional — they're the masked defects this lint exists to
# surface). Each must be triaged: file a fresh tracker comment,
# close-and-flip, or document the deliberate keep-mask in a fresh
# 14-day-renewable tracker. After main is clean for 3 days,
# follow-up PR flips this workflow's continue-on-error to false.
# Tracking: mc#774.
#
# Cross-links
# -----------
# - mc#774 (the RFC that specs this lint)
# - mc#774 (the empirical masked-3-weeks case)
# - feedback_chained_defects_in_never_tested_workflows
# - feedback_behavior_based_ast_gates
# - feedback_strict_root_only_after_class_a
#
# Auth: DRIFT_BOT_TOKEN — same persona used by ci-required-drift.yml
# (provisioned under internal#329). Auto-injected GITHUB_TOKEN is
# insufficient because `internal#NNN` references cross repositories
# (molecule-core → molecule-ai/internal).

on:
  pull_request:
    types: [opened, synchronize, reopened]
    paths:
      - '.gitea/workflows/**'
      - '.gitea/scripts/lint_continue_on_error_tracking.py'
      - 'tests/test_lint_continue_on_error_tracking.py'
  push:
    branches: [main, staging]
    paths:
      - '.gitea/workflows/**'
      - '.gitea/scripts/lint_continue_on_error_tracking.py'
  schedule:
    # Daily at 13:11 UTC — off-peak, prime-staggered from the other
    # Tier-2 lint schedules (ci-required-drift runs hourly :00).
    - cron: '11 13 * * *'
  workflow_dispatch:

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

permissions:
  contents: read

concurrency:
  group: lint-coe-tracking-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  # bp-exempt: meta-lint for masked jobs; tracked separately until masks are burned down.
  lint:
    name: lint-continue-on-error-tracking
    runs-on: ubuntu-latest
    timeout-minutes: 20
    # Phase 3 (RFC #219 §1): surface masked defects without blocking
    # PRs. Pre-existing continue-on-error: true directives on main
    # all violate this lint at first — intentional. Flip to false
    # follow-up after main is clean for 3 days. mc#774.
    # mc#774: pre-existing continue-on-error mask; root-fix and remove, do not renew silently.
    continue-on-error: true  # mc#774 Phase 3 mask — 14d forced-renewal cadence
    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-continue-on-error-tracking
        env:
          GITEA_TOKEN: ${{ secrets.DRIFT_BOT_TOKEN }}
          GITEA_HOST: git.moleculesai.app
          REPO: ${{ github.repository }}
          INTERNAL_REPO: molecule-ai/internal
          WORKFLOWS_DIR: .gitea/workflows
          MAX_AGE_DAYS: '14'
        run: python3 .gitea/scripts/lint_continue_on_error_tracking.py
      - name: Run lint-continue-on-error-tracking unit tests
        run: |
          python -m pip install --quiet pytest
          python3 -m pytest tests/test_lint_continue_on_error_tracking.py -v
