Andrushika commented on code in PR #73378: URL: https://github.com/apache/airflow/pull/73378#discussion_r4312034257
########## dev/breeze/doc/ci/08_fork_ci_for_external_contributors.md: ########## @@ -0,0 +1,769 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + --> + +<!-- START doctoc generated TOC please keep comment here to allow auto update --> +<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> + +- [Fork CI for external contributors (AIP-120)](#fork-ci-for-external-contributors-aip-120) + - [Goals and non-goals](#goals-and-non-goals) + - [Overview](#overview) + - [Configuration](#configuration) + - [Eligibility](#eligibility) + - [Project CI gate](#project-ci-gate) + - [Fork mode CI](#fork-mode-ci) + - [Reconciler](#reconciler) + - [Monitor workflow](#monitor-workflow) + - [`breeze ci audit`](#breeze-ci-audit) + - [Documentation changes](#documentation-changes) + - [Rollout and rollback](#rollout-and-rollback) + - [Security considerations](#security-considerations) + - [Known limitations](#known-limitations) + - [Testing](#testing) + +<!-- END doctoc generated TOC please keep comment here to allow auto update --> + +# Fork CI for external contributors (AIP-120) + +This document is the design of the CI changes implementing +[AIP-120: Run CI for external contributions in contributor forks](https://cwiki.apache.org/confluence/spaces/AIRFLOW/pages/451974711/AIP-120+Run+CI+for+external+contributions+in+contributor+forks). +The policy itself and its rationale live in the AIP and in the dev list discussions +([first](https://lists.apache.org/thread/w74655o8y1jzrzcnygntyyovocf7zmzj), +[second](https://lists.apache.org/thread/ky392mrrcmj312y1r276r30p584n804c), which agreed to +start with an opt-in pilot). +This document describes how the policy is mechanised. + +## Goals and non-goals + +Goals: + +- External contributors get CI feedback from a plain `git push` to their fork. No pull request + against their own fork, no manual workflow dispatch, no sorting through workflow files. +- Project CI (`apache/airflow` runners) does not run for gated pull requests. The decision is + made by one function with one configuration file. +- A single scheduled reconciler owns the lifecycle of gated pull requests: labelling, drafting, + undrafting, the state comment, the nudge after workflows are enabled, closing after + inactivity, and closing a pull request that tampered with the gate to get project CI. It is + idempotent, so missed or duplicated scheduled runs are harmless. +- Maintainers and contributors see the fork CI state where they already look: a commit status + on the pull request head, red / yellow / green, linking to the run in the fork. +- A contributor can verify their whole setup locally with `breeze ci audit` before pushing. +- Everything ships dormant and is switched on by a one-line configuration change. +- Before it is switched on for everyone, the whole flow can be piloted on real traffic by + committers on single pull requests and by volunteer external contributors on all their new + pull requests, without affecting anyone else. + +Non-goals: + +- The one-time reset of the open pull request backlog (AIP-120 §9.1). The design makes it a + configuration change, but it is decided separately. +- Any change to how committer, collaborator or exempted-organisation pull requests are tested. +- Event-driven mutation of pull requests. Nothing in this design uses `pull_request_target`. + All writes come from the scheduled reconciler. + +## Overview + +``` + contributor fork apache/airflow + ┌──────────────────────────────────┐ ┌──────────────────────────────────────────┐ + │ git push feature-branch │ │ pull_request event │ + │ └─ ci-amd.yml (push trigger) │ │ └─ ci-amd.yml │ + │ gate job: fork -> run │ │ gate job: eligibility -> skip │ + │ build-info: fork mode │ │ (build-info and everything after │ + │ changed files from │ │ are skipped for gated PRs) │ + │ compare API vs upstream │ │ │ + │ public runners only │ │ fork-ci-monitor.yml (every 15 minutes) │ + └──────────────────────────────────┘ │ └─ breeze ci fork-ci-reconcile │ + ▲ │ discover new gated PRs -> label │ + │ GraphQL: checkSuites │ per labelled PR: fork state -> │ + │ REST: workflow state │ draft/undraft, labels, comment, │ + └────────────────────────│ commit status with run link, │ + │ close after 7 days or when the │ + │ PR tampered with the gate │ + │ unknown failures -> Slack │ + └──────────────────────────────────────────┘ +``` + +Four pieces, in dependency order: + +1. **Eligibility** — `dev/breeze/src/airflow_breeze/utils/fork_ci_gate.py`, one pure function + and one configuration file. Used by the gate job and by the reconciler. +2. **Project CI gate** — a `gate` job at the head of `ci-amd.yml`. +3. **Fork mode CI** — a push trigger plus fork-aware selective checks. +4. **Reconciler** — `breeze ci fork-ci-reconcile`, run by `fork-ci-monitor.yml` on a schedule. + +Plus `breeze ci audit` for the contributor's local setup, and documentation. + +## Configuration + +One file, `.github/fork-ci-gate.toml`, parsed with the standard library `tomllib`: + +```toml +# Fork CI gate (AIP-120). See dev/breeze/doc/ci/08_fork_ci_for_external_contributors.md + +# Master switch. When false the gate job lets every pull request run project CI, except +# pull requests labelled `use fork ci` and pull requests of [pilot] participants. Push +# filtering for apache/airflow branches applies regardless. +enabled = false + +# Pull requests created before this instant are never gated, whatever their author. +# Moving the date back is how the backlog reset (AIP-120 §9.1) would be applied. +enabled_since = 2026-01-01T00:00:00Z + +# Days without any fork CI activity after which a gated pull request is closed. +close_after_days = 7 + +# Organisations exempted under AIP-120 §2.2. Keys are display names used in comments, +# values are GitHub logins. Changes go through a reviewed pull request. +[allowlist] +# "Example Org" = ["login-one", "login-two"] + +# Opt-in pilot participants. Keys are GitHub logins, values are the instant they joined. +# Their pull requests created at or after that instant are gated whether or not `enabled` +# is set. Contributors opt in and out with a pull request editing this table. +[pilot] +# "login-three" = 2026-10-20T00:00:00Z +``` + +The file lives in the repository rather than in an Actions variable because ASF projects +cannot self-serve Actions variables, and because exemption changes should be reviewed like +any other change. The gate job reads it from the pull request's *base* branch, never from +the pull request's own tree, so a pull request cannot change its own eligibility. + +## Eligibility + +`fork_ci_gate.decide(event, config) -> Decision` is a pure function. `event` carries the +fields both callers have: event name, repository, ref, head repository, author login, +author association, pull request creation time and the pull request's current labels. +`Decision` is `run` (a bool) plus a `reason` from a fixed set, used in logs, comments and by +the reconciler. + +The rules, first match wins. The push rules are independent of `enabled` because the `'**'` +push trigger exists whether or not the gate is on, and today no CI runs for pushes to +`main` or to ad-hoc branches in `apache/airflow` (post-merge coverage is the cron canary). +The label and pilot rules are also independent of `enabled`; see "Override labels" and +"Contributor pilot" below. + +The `fork_schedule` rule exists because enabling `ci-amd.yml` in a fork also enables its +cron trigger, and selective checks force the full matrix for `schedule` events. Without it +every enabled fork would run the full canary matrix twice a day on the contributor's +concurrency cap. A manual `workflow_dispatch` in a fork still runs. + +| Rule | Decision | Reason | +|---|---|---| +| Event is `schedule` and repository is not `apache/airflow` | skip | `fork_schedule` | +| Event is `schedule` or `workflow_dispatch` | run | `scheduled` | +| Event is `push` and repository is not `apache/airflow` | run | `fork_push` | +| Event is `push` to `apache/airflow` and the branch matches the pre-existing push patterns (`v*-test`, `providers-*/v*`, `chart/v*x-test`, `airflow-ctl/v*-test`) | run | `canonical_branch` | +| Event is `push` to `apache/airflow`, any other branch | skip | `adhoc_branch` | +| Pull request carries `use project ci` | run | `label_project_ci` | +| Pull request carries `use fork ci` | skip | `label_fork_ci` | +| Author login is in `[pilot]` and the pull request was created at or after that login's join instant | skip | `pilot` | +| `enabled` is false | run | `gate_disabled` | Review Comment: We might need to sync this design doc with the copy in the stack. For example, there `head_is_upstream` now comes before `use fork ci`, and `base_without_gate` and the `needs project ci` label are new. The ADR also still says Slack gets two expected outcomes, while `green_unverified` posts too. --- Drafted-by: Claude Code (Opus 5.5); reviewed by @Andrushika before posting -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
