Skip to content
Merged
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
113 changes: 88 additions & 25 deletions .github/workflows/assign-rfc-number.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,28 @@ name: RFC Number Assigner
# 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.
# 3. dart run bin/assign_rfc_number.dart runs from tools/ (passing --target-dir="../target/rfc")
# executing only trusted bytecode and package configurations.
# 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]

# Workflow-level default permissions:
# Explicitly set to empty ({}) to enforce least privilege by default.
# Any job added to this file starts with zero permissions unless explicitly
# granted in its own job-level `permissions:` block below.
permissions: {}

jobs:
assign-number:
name: assign-number
if: github.event.label.name == 'assign-rfc-number'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
contents: write # Required to push assigned RFC commit to same-repository PR branches
pull-requests: write # Required to add/remove RFC lifecycle labels on the PR
issues: write # Required to post status and fallback comments on the PR

steps:
# --- Sandbox 1: Trusted Tooling Setup ---
Expand Down Expand Up @@ -47,51 +55,106 @@ jobs:
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
# Note: `allow-unsafe-pr-checkout: true` opts out of actions/checkout v7's blanket
# block on fork checkouts in `pull_request_target`. This is safe because:
# 1. We only sparse-checkout `rfc/` markdown documents into a separate `target/` directory.
# 2. `persist-credentials: false` prevents token leakage into `.git/config`.
# 3. No code, scripts, or dependencies from `target/` are ever executed.
allow-unsafe-pr-checkout: true

# --- Execution & Delivery ---
# Runs strictly in the trusted 'tools' directory so Dart resolves package
# configs from 'tools/' and git ls-tree queries 'origin/main' directly from
# the trusted repository checkout before any git credentials are configured.
- name: Assign RFC Number
id: assign
working-directory: target
run: dart run ../tools/bin/assign_rfc_number.dart
working-directory: tools
run: dart run bin/assign_rfc_number.dart --target-dir="../target/rfc"

# Update labels BEFORE pushing changes:
# When pushing with FLUTTERACTIONSBOT_RFC_TOKEN (a user PAT), GitHub immediately
# triggers `pull_request` (`synchronize`) workflows like `rfc-lint.yml`.
# Applying `rfc-assigned` first ensures `rfc-lint` sees the label and permits
# the newly assigned non-0000 RFC number.
- name: Update PR Labels
if: success()
run: |
gh pr edit "$PR_NUMBER" --repo "$REPO" \
--add-label rfc-assigned \
--remove-label assign-rfc-number || true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}

# Configures the GitHub CLI credential helper (`gh auth setup-git`) using
# FLUTTERACTIONSBOT_RFC_TOKEN (falling back to GITHUB_TOKEN if unset) so
# remote git operations (`git push`) authenticate without embedding
# credentials into `.git/config` or remote URLs.
- name: Configure Git
if: success()
run: |
gh auth setup-git
git config --global user.name "flutteractionsbot"
git config --global user.email "<flutter-actions-bot@google.com>"
env:
GH_TOKEN: ${{ secrets.FLUTTERACTIONSBOT_RFC_TOKEN || secrets.GITHUB_TOKEN }}

- name: Commit and Push Changes
if: success()
working-directory: target
run: |
git -c core.hooksPath=/dev/null add -A rfc/
git -c core.hooksPath=/dev/null commit --no-verify -m "docs(rfc): assign RFC ${RFC_ID}"
git -c core.hooksPath=/dev/null push "https://github.com/${REPO}.git" "HEAD:${PR_HEAD_REF}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.FLUTTERACTIONSBOT_RFC_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()
run: |
gh pr comment "$PR_NUMBER" --repo "$REPO" --body "Assigned RFC ${RFC_ID}." || true
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()
run: |
gh pr edit "$PR_NUMBER" --repo "$REPO" --remove-label assign-rfc-number || true
if [ -n "$RFC_ID" ]; then
cat <<EOF > comment.txt
Assigned **RFC ${RFC_ID}**, but failed to automatically push the commit to your pull request branch (this commonly happens if "Allow edits from maintainers" is disabled on a fork PR, or if the fork belongs to an organization).

Please examine the [workflow run logs](${RUN_URL}) and run the following commands locally on your branch to apply the assigned RFC number:

\`\`\`sh
dart run bin/assign_rfc_number.dart
git add -A rfc/
git commit -m "docs(rfc): assign RFC ${RFC_ID}"
git push
\`\`\`
EOF
else
cat <<EOF > comment.txt
Failed to automatically assign an RFC number.

Please examine the [workflow run logs](${RUN_URL}) to review the error details, or run the tool locally to diagnose and assign your RFC number:

\`\`\`sh
dart run bin/assign_rfc_number.dart
\`\`\`
EOF
fi
gh pr comment "$PR_NUMBER" --repo "$REPO" --body-file comment.txt || true
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
RFC_ID: ${{ steps.assign.outputs.rfc_id }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
24 changes: 12 additions & 12 deletions .github/workflows/rfc-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ on:
paths:
- 'rfc/**'

permissions: {}

jobs:
lint-rfcs:
name: lint-rfcs
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read

steps:
- name: Checkout Code
Expand All @@ -35,8 +36,6 @@ jobs:
- 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
Expand All @@ -47,33 +46,34 @@ jobs:
FILES=$(git diff --name-only --diff-filter=ACMR "$BASE_TARGET" -- 'rfc/*.md' 2>/dev/null || true)
fi
if [ -n "$FILES" ]; then
EOF_MARKER="EOF_$(openssl rand -hex 16)"
echo "has_changes=true" >> "$GITHUB_OUTPUT"
{
echo "files<<EOF"
echo "files<<$EOF_MARKER"
echo "$FILES"
echo "EOF"
echo "$EOF_MARKER"
} >> "$GITHUB_OUTPUT"
else
echo "has_changes=false" >> "$GITHUB_OUTPUT"
fi
env:
BASE_REF: ${{ github.base_ref }}

- 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: |
mapfile -t FILES_ARRAY <<< "$CHANGED_FILES"
dart run bin/rfc_lint.dart \
--enforce-drafts \
--labels "$LABELS" \
--github-actions \
$CHANGED_FILES
-- "${FILES_ARRAY[@]}"
env:
LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
CHANGED_FILES: ${{ steps.changed-rfcs.outputs.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 \
--github-actions
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ on:
- 'analysis_options.yaml'
- '.github/workflows/test.yml'

permissions: {}

jobs:
test:
name: Dart Format, Analyze, and Test
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/validate-rfc-number.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ on:
paths:
- 'rfc/**'

permissions: {}

jobs:
validate-rfc-number:
name: validate-rfc-number
Expand Down
8 changes: 7 additions & 1 deletion bin/assign_rfc_number.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import 'package:rfc_tools/src/assigner.dart';

void main(List<String> arguments) async {
final parser = ArgParser()
..addOption(
'target-dir',
defaultsTo: RfcAssigner.rfcDir,
help: 'Directory containing RFC markdown documents.',
)
..addOption(
'target-file',
help:
Expand Down Expand Up @@ -42,11 +47,12 @@ void main(List<String> arguments) async {
return;
}

final targetDir = results.option('target-dir') ?? RfcAssigner.rfcDir;
final targetFile = results.rest.firstOrNull ?? results.option('target-file');
final dryRun = results.flag('dry-run');

const fs = LocalFileSystem();
final assigner = RfcAssigner(fs: fs);
final assigner = RfcAssigner(fs: fs, rfcDirPath: targetDir);

try {
stdout.writeln('Assigning RFC number...');
Expand Down
Loading
Loading