Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions .github/workflows/assign-rfc-number.yml
Original file line number Diff line number Diff line change
@@ -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
95 changes: 95 additions & 0 deletions .github/workflows/rfc-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
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: 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<<EOF"
echo "$FILES"
echo "EOF"
} >> "$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 \
$CHANGED_FILES

- 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 (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'

48 changes: 48 additions & 0 deletions .github/workflows/validate-rfc-number.yml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions .markdownlint.yaml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions rfc/000.0001-flutter-architecture-and-reference-taxonomy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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" <user@example.com>` (e.g., `'"John McDole" <codefu@google.com>'`).

Expand Down Expand Up @@ -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]`).
8 changes: 8 additions & 0 deletions rfc/000.0002-flutter-rfc-review-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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`
Expand All @@ -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.
Expand Down Expand Up @@ -157,18 +162,21 @@ 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.

---

## 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.
Expand Down
Loading