From ed28e5d456188357d5671a19a53fdcaf40ee82cd Mon Sep 17 00:00:00 2001 From: John McDole Date: Thu, 3 Sep 2026 16:11:32 -0700 Subject: [PATCH 1/2] ci(actions): add GitHub Actions workflows for linting, validation, and assignment --- .github/workflows/assign-rfc-number.yml | 97 +++++++++++++++++++++++ .github/workflows/rfc-lint.yml | 60 ++++++++++++++ .github/workflows/validate-rfc-number.yml | 48 +++++++++++ 3 files changed, 205 insertions(+) create mode 100644 .github/workflows/assign-rfc-number.yml create mode 100644 .github/workflows/rfc-lint.yml create mode 100644 .github/workflows/validate-rfc-number.yml diff --git a/.github/workflows/assign-rfc-number.yml b/.github/workflows/assign-rfc-number.yml new file mode 100644 index 0000000..5dfeaa8 --- /dev/null +++ b/.github/workflows/assign-rfc-number.yml @@ -0,0 +1,97 @@ +name: RFC Number Assigner + +# Security Note: pull_request_target runs in the context of the base repository (main) +# with write permissions. To prevent running code from untrusted forks, this +# workflow implements a strict two-checkout isolation model using separate sibling directories: +# 1. tools/: Contains trusted tooling from main. dart pub get runs only here. +# 2. target/: Contains only rfc/ markdown content from the PR branch via sparse checkout. +# 3. dart run ../tools/bin/assign_rfc_number.dart runs from target/ executing only trusted bytecode. +# 4. Gated by the maintainer-applied 'assign-rfc-number' label. +on: + pull_request_target: # zizmor: ignore[dangerous-triggers] Isolated two-checkout model prevents code execution from untrusted PR + types: [labeled] + +jobs: + assign-number: + if: github.event.label.name == 'assign-rfc-number' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + issues: write + + steps: + # --- Sandbox 1: Trusted Tooling Setup --- + - name: Checkout Trusted Tooling (main) + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: tools + persist-credentials: false + + - name: Setup Dart + uses: dart-lang/setup-dart@6afc89df92d6eb3834022f73cd65adc8cdfcb92d # v1.8.1 + + - name: Install Tooling Dependencies + working-directory: tools + run: dart pub get + + # --- Sandbox 2: Untrusted PR Content Checkout --- + - name: Checkout Pull Request Branch (RFC directory only) + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + path: target + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + sparse-checkout: | + rfc + sparse-checkout-cone-mode: false + fetch-depth: 0 + persist-credentials: false + + - name: Configure Git + working-directory: target + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git fetch "https://github.com/${{ github.repository }}.git" main:origin/main + + # --- Execution & Delivery --- + - name: Assign RFC Number + id: assign + working-directory: target + run: dart run ../tools/bin/assign_rfc_number.dart + + - name: Commit and Push Changes + if: success() + working-directory: target + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RFC_ID: ${{ steps.assign.outputs.rfc_id }} + PR_HEAD_REF: ${{ github.event.pull_request.head.ref }} + REPO: ${{ github.event.pull_request.head.repo.full_name }} + run: | + git add -A rfc/ + git commit -m "docs(rfc): assign RFC ${RFC_ID}" + git push "https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" "HEAD:${PR_HEAD_REF}" + + - name: Handle Success + if: success() + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RFC_ID: ${{ steps.assign.outputs.rfc_id }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REPO: ${{ github.repository }} + run: | + gh pr edit "$PR_NUMBER" --repo "$REPO" --remove-label assign-rfc-number || true + gh pr edit "$PR_NUMBER" --repo "$REPO" --add-label rfc-assigned || true + gh pr comment "$PR_NUMBER" --repo "$REPO" --body "Assigned RFC ${RFC_ID}." || true + + - name: Handle Failure + if: failure() + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REPO: ${{ github.repository }} + run: | + gh pr edit "$PR_NUMBER" --repo "$REPO" --remove-label assign-rfc-number || true + gh pr comment "$PR_NUMBER" --repo "$REPO" --body "Failed to automatically assign RFC number. Check workflow logs for details." || true diff --git a/.github/workflows/rfc-lint.yml b/.github/workflows/rfc-lint.yml new file mode 100644 index 0000000..af3975e --- /dev/null +++ b/.github/workflows/rfc-lint.yml @@ -0,0 +1,60 @@ +name: RFC Linter + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened, labeled, unlabeled] + paths: + - 'rfc/**' + merge_group: + types: [checks_requested] + push: + branches: [main] + paths: + - 'rfc/**' + +jobs: + lint-rfcs: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + + steps: + - name: Checkout Code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: ${{ github.event_name == 'pull_request' && 0 || 1 }} + persist-credentials: false + + - name: Setup Dart + uses: dart-lang/setup-dart@6afc89df92d6eb3834022f73cd65adc8cdfcb92d # v1.8.1 + + - name: Install Dependencies + run: dart pub get + + - name: Run RFC Linter (PR Mode) + if: github.event_name == 'pull_request' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }} + run: | + dart run bin/rfc_lint.dart \ + --enforce-drafts \ + --labels "$LABELS" \ + --validate-github-users \ + --github-actions + + - name: Run RFC Linter (Merge Queue & Main Mode) + if: github.event_name != 'pull_request' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + dart run bin/rfc_lint.dart \ + --validate-github-users \ + --github-actions + + - name: Markdown Lint + uses: DavidAnson/markdownlint-cli2-action@05f32210e84442804257b2a6f20b273450ec8265 # v19 + with: + globs: 'rfc/**/*.md' diff --git a/.github/workflows/validate-rfc-number.yml b/.github/workflows/validate-rfc-number.yml new file mode 100644 index 0000000..033e1b8 --- /dev/null +++ b/.github/workflows/validate-rfc-number.yml @@ -0,0 +1,48 @@ +name: RFC Semantic Validator + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + paths: + - 'rfc/**' + merge_group: + types: [checks_requested] + push: + branches: [main] + paths: + - 'rfc/**' + +jobs: + validate-rfc-number: + name: validate-rfc-number + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout Code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: ${{ github.event_name == 'pull_request' && 0 || 1 }} + persist-credentials: false + + - name: Setup Dart + uses: dart-lang/setup-dart@6afc89df92d6eb3834022f73cd65adc8cdfcb92d # v1.8.1 + + - name: Install Dependencies + run: dart pub get + + - name: Validate (PR Mode) + if: github.event_name == 'pull_request' + run: | + dart run bin/validate_rfc_number.dart \ + --check-main \ + --github-actions + + - name: Validate (Merge Queue & Main Mode) + if: github.event_name == 'merge_group' || (github.event_name == 'push' && github.ref == 'refs/heads/main') + run: | + dart run bin/validate_rfc_number.dart \ + --no-drafts \ + --github-actions From c347b99f65d316f5d455f9097255ed464d506811 Mon Sep 17 00:00:00 2001 From: John McDole Date: Fri, 4 Sep 2026 10:06:28 -0700 Subject: [PATCH 2/2] fix: markdown warnings --- .github/workflows/rfc-lint.yml | 41 +++++++++++++++++-- .markdownlint.yaml | 2 + ...ter-architecture-and-reference-taxonomy.md | 2 + rfc/000.0002-flutter-rfc-review-process.md | 8 ++++ 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rfc-lint.yml b/.github/workflows/rfc-lint.yml index af3975e..b7552a8 100644 --- a/.github/workflows/rfc-lint.yml +++ b/.github/workflows/rfc-lint.yml @@ -33,17 +33,44 @@ jobs: - name: Install Dependencies run: dart pub get - - name: Run RFC Linter (PR Mode) + - name: Get Changed RFCs + id: changed-rfcs if: github.event_name == 'pull_request' + env: + BASE_REF: ${{ github.base_ref }} + run: | + BASE_TARGET="origin/${BASE_REF:-main}" + if ! git rev-parse --verify "$BASE_TARGET" >/dev/null 2>&1; then + BASE_TARGET="main" + fi + FILES=$(git diff --name-only --diff-filter=ACMR "$BASE_TARGET"...HEAD -- 'rfc/*.md' 2>/dev/null || true) + if [ -z "$FILES" ]; then + FILES=$(git diff --name-only --diff-filter=ACMR "$BASE_TARGET" -- 'rfc/*.md' 2>/dev/null || true) + fi + if [ -n "$FILES" ]; then + echo "has_changes=true" >> "$GITHUB_OUTPUT" + { + echo "files<> "$GITHUB_OUTPUT" + else + echo "has_changes=false" >> "$GITHUB_OUTPUT" + fi + + - name: Run RFC Linter (PR Mode) + if: github.event_name == 'pull_request' && steps.changed-rfcs.outputs.has_changes == 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }} + CHANGED_FILES: ${{ steps.changed-rfcs.outputs.files }} run: | dart run bin/rfc_lint.dart \ --enforce-drafts \ --labels "$LABELS" \ --validate-github-users \ - --github-actions + --github-actions \ + $CHANGED_FILES - name: Run RFC Linter (Merge Queue & Main Mode) if: github.event_name != 'pull_request' @@ -54,7 +81,15 @@ jobs: --validate-github-users \ --github-actions - - name: Markdown Lint + - name: Markdown Lint (PR Mode) + if: github.event_name == 'pull_request' && steps.changed-rfcs.outputs.has_changes == 'true' + uses: DavidAnson/markdownlint-cli2-action@05f32210e84442804257b2a6f20b273450ec8265 # v19 + with: + globs: ${{ steps.changed-rfcs.outputs.files }} + + - name: Markdown Lint (Full Repository) + if: github.event_name != 'pull_request' uses: DavidAnson/markdownlint-cli2-action@05f32210e84442804257b2a6f20b273450ec8265 # v19 with: globs: 'rfc/**/*.md' + diff --git a/.markdownlint.yaml b/.markdownlint.yaml index bcb0f89..528e09e 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -1,4 +1,6 @@ default: true # Enable all standard markdownlint rules by default MD013: false # Do not enforce line lengths (diff churning, table lengths, diagrams etc) +MD025: + front_matter_title: "" # Do not treat frontmatter title as H1 heading (RFC H1 is in body) MD033: false # Allow inline HTML (badges, centered logos/images, details/summary folds) MD041: true # Enforce top-level heading (# RFC AAA.NNNN: Title) after frontmatter diff --git a/rfc/000.0001-flutter-architecture-and-reference-taxonomy.md b/rfc/000.0001-flutter-architecture-and-reference-taxonomy.md index 74102ab..3fa3188 100644 --- a/rfc/000.0001-flutter-architecture-and-reference-taxonomy.md +++ b/rfc/000.0001-flutter-architecture-and-reference-taxonomy.md @@ -157,6 +157,7 @@ Valid statuses: `draft`, `stable`, `deprecated` (conforming to the Open Knowledg #### Author Attribution Formats The `authors:` list supports two attribution formats: + 1. **GitHub User Profile (Preferred):** Full URL to the author's GitHub profile (e.g., `https://github.com/octocat`). This is strongly preferred because it provides durable attribution linked directly to GitHub review activity, mentions, and issue tracking without exposing personal email addresses. 2. **RFC 5322 Mailbox Format:** Display name and email address formatted as `"Display Name" ` (e.g., `'"John McDole" '`). @@ -189,5 +190,6 @@ For details on the review process, see [RFC 000.0002: Flutter RFC Review & Decis ## Cross-Cutting Proposals When a proposal spans multiple subsystems (e.g., Impeller graphics backend work requiring changes in the iOS embedder): + 1. **Primary Category:** Assign the RFC number based on the subsystem where the primary architectural impact or implementation effort resides (e.g., `210` Graphics Backends). 2. **Secondary Tagging:** List all other affected subsystems in the `tags:` list of the YAML frontmatter (e.g., `tags: [210-graphics, 420-ios]`). diff --git a/rfc/000.0002-flutter-rfc-review-process.md b/rfc/000.0002-flutter-rfc-review-process.md index af302a7..0e820dc 100644 --- a/rfc/000.0002-flutter-rfc-review-process.md +++ b/rfc/000.0002-flutter-rfc-review-process.md @@ -39,12 +39,15 @@ The vast majority of engineering tasks in Flutter do **not** require an RFC. The | **Full Design Doc (RFC)** | Architectural changes, cross-subsystem boundary shifts, new primitives, breaking changes, file formats, style guide, or governance. | Version-controlled Markdown in `flutter/rfc` | Formal RFC review, design review meeting (consultative), Subsystem TL approval | **MUST** | ### 1. Does NOT Require an RFC + * **Bug fixes, performance optimizations, and internal refactors** that preserve existing API contracts and subsystem boundaries. * **One-Pagers**: Localized features or tasks contained within a single subsystem (Category `AAA`). These **SHOULD** be documented directly within a GitHub issue or a clear Pull Request description. * **Two-Pagers**: Projects with broader scope that consume other teams' APIs or subsystems in new ways without altering their public API/ABI contracts. These **SHOULD** be handled via Discord, GitHub Issues, or lightweight design docs with informal alignment between team TLs. Authors **MAY** optionally author these as lightweight RFCs if they seek broader community feedback, but a formal RFC is not required unless system boundaries or contracts change. ### 2. MUST Require an RFC (Full Design Docs) + A proposal **MUST** go through the RFC process if it meets any of the following criteria: + * **Cross-Subsystem Architectural Impact**: Changes that cross or alter boundaries between major Flutter subsystems (e.g., Framework `100` $\leftrightarrow$ Engine `200`, Engine `200` $\leftrightarrow$ Embedders `400`). * **New Foundational Primitives**: Introducing new rendering backends, compilers, execution platforms, or embedder shells. * **File Formats & Protocols**: Specifying, altering, or deprecating file formats, data wire protocols, asset packaging schemes, or tooling interop protocols (e.g., tool daemon protocols, VM Service extensions). @@ -105,6 +108,7 @@ sequenceDiagram ``` ### Stage 1: Proposal & Draft PR (`AAA.0000`) + 1. The author selects the primary 3-digit category `AAA` from [RFC 000.0001: Flutter Architecture & Reference Taxonomy](000.0001-flutter-architecture-and-reference-taxonomy.md). 2. The author opens a **Draft Pull Request** against `flutter/rfc`: * File path: `rfc/AAA.0000-kebab-case-title.md` @@ -127,6 +131,7 @@ Technical iteration occurs primarily through asynchronous GitHub PR line comment For high-impact, cross-cutting, or contentious proposals requiring broad visibility or synchronous architectural discussion, the proposal **SHOULD** be presented at an **RFC Design Review meeting**. The Shepherd is responsible for scheduling this 45-minute meeting on the appropriate calendar. #### Design Review Meeting Guidelines + 1. **Lead Time**: Authors **SHOULD** have an active `AAA.0000` Draft PR open on GitHub for at least **7 calendar days** prior to the scheduled meeting date to ensure attendees have adequate review time. 2. **Pre-Alignment**: Authors **MUST** pre-align with reviewers and incorporate initial feedback from the Subsystem TLs overseeing the affected systems *before* the meeting is scheduled. Pre-alignment does not mean agreement. It means questions that are answered are resolved and questions that remain are clear discussion topics. 3. **Problem Issue Tagging**: Authors **SHOULD** ensure the tracking issue in `flutter/flutter` has the `design doc` label applied. This alerts external contributors via Discord (`#hidden-chat`) and internal subscribers via the Dart GitHub label notifier. @@ -157,11 +162,13 @@ When discussion converges and open threads are addressed, the Shepherd **MAY** c * Once all required approvals are submitted (and FCP concludes, if initiated), the author inspects merged files in `rfc/` under category `AAA` per the numbering rules in [RFC 000.0001](000.0001-flutter-architecture-and-reference-taxonomy.md). * The next available sequential index (`.0001`, `.0002`, ...) is determined. * The author renames `rfc/AAA.0000-title.md` to `rfc/AAA.NNNN-title.md` and updates the frontmatter: + ```yaml rfc: 'AAA.NNNN' status: stable updated: YYYY-MM-DDTHH:MM:SSZ ``` + 3. **Merge**: The Shepherd merges the PR into `main`. Once merged, the RFC number `AAA.NNNN` is permanent and immutable. --- @@ -169,6 +176,7 @@ When discussion converges and open threads are addressed, the Shepherd **MAY** c ## Rejections & Withdrawn Proposals To keep the repository's `main` branch clean and compliant with the Open Knowledge Format (OKF) schema: + * If consensus cannot be reached, an unresolvable blocker emerges, or the author chooses not to proceed, the PR is **closed unmerged**. * The Shepherd posts a summary comment documenting the consensus findings and technical rationale for rejection. * The label `status: rejected` or `status: withdrawn` is applied to the closed PR.