Repository navigation
126 lines (121 loc) · 6.63 KB
/
Copy pathdesign-gate.yml
File metadata and controls
126 lines (121 loc) · 6.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
name: Design gate
# The fleet-wide contribution gate, in its one canonical home. Every repository
# that adopts it calls THIS file with `uses:`; nobody copies it. A contributor
# who has learned the rule in one repository has learned it in all of them, and
# a fix to the rule lands once. See docs/contribution-gate.md for the caller
# file, the secrets, the labels, and the branch-protection contract.
#
# Contributor work reaches us as a pull request, and the design conversation
# used to start only once the code already existed — on an issue opened after
# the fact, or on a competing PR. This workflow moves that conversation in
# front of the code: a PR is reviewable and mergeable only once the issue it
# links carries a maintainer's `design-approved` label.
#
# GitHub has no "issue first" setting, so the rule is built from two
# primitives: the check this workflow reports (made a required status check
# through branch protection — an operator setting, not part of this file) and
# the pull request's draft state.
#
# All parsing and decision logic lives in the composite action beside this
# file, in .github/actions/design-gate, so it can be unit tested offline; see
# scripts/design-gate.test.mjs.
#
# SECURITY PROPERTY, which is the whole point of the shape below.
# `pull_request_target` runs in the base repository's context with access to
# its secrets, so contributor code from the pull request head must never be
# checked out or executed. This workflow therefore contains NO
# `actions/checkout` step and NO `git` invocation: the runner fetches the gate
# action's files itself, so there is no ref for a caller to supply and nothing
# a caller can pass that reaches a checkout. That is stronger than checking out
# the base ref correctly, because there is no argument left to get wrong, and
# `scripts/design-gate.test.mjs` asserts it against this file and the action.
#
# The triggers live in the CALLER, because a reusable workflow cannot add any.
# The canonical caller in docs/contribution-gate.md includes `labeled` and
# `unlabeled` in its `pull_request_target` types, so a maintainer applying
# `trivial` to an already-failing PR re-runs the gate and turns the check
# green. A caller that leaves them out only re-evaluates on the next push or a
# manual re-run from the checks tab.
# The gate takes no secrets: it holds no credential beyond the job's own
# GITHUB_TOKEN. A caller that passes one is refused when the workflow is
# parsed, which is the intended signal that it is still on the old contract.
on:
workflow_call:
inputs:
# Under `pull_request_target`, the caller workflow is read from the base
# branch, so a pull request cannot change its own bot exemptions.
owner-bots:
description: Whitespace- or comma-separated login:id pairs for the repository's own bots.
type: string
required: false
default: ""
concurrency:
group: design-gate-${{ github.event.pull_request.number || github.event.issue.number }}
# Pull request runs are pure re-evaluations, so superseding one is free. The
# issue arm marks PRs ready and must not be interrupted half way.
cancel-in-progress: ${{ github.event_name == 'pull_request_target' }}
jobs:
# This job's own status reports as `<caller job id> / <this job id>`, because
# that is how GitHub names a job called from a reusable workflow. That name
# is informational and must NOT be the required status check: half of it
# belongs to the calling repository, so renaming a caller's job would break
# its own branch protection. The required check is `design-gate`, published
# on the PR head by the gate script itself, which owns the name outright.
design-gate:
# The event arm is decided here rather than in the caller so that a caller
# cannot skip the gate by leaving an `if:` out.
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read # fetch the gate action
issues: read # read the linked issue's labels
pull-requests: write # post the gate comment, convert back to draft
checks: write # publish the design-gate check run on the PR head
steps:
# Every write runs on this job's own GITHUB_TOKEN, with exactly the
# permissions above. Under `pull_request_target` that token carries the
# declared write scopes even on pull requests from forks (it is the
# `pull_request` trigger whose token is read-only there), so commenting
# and draft conversion need no App token. Keeping an App private key out
# of this job matters because it runs in public repositories: a key that
# can read private repositories must never be reachable from here.
#
# Every action this reusable workflow runs is pinned to a commit, because
# it runs in other repositories with their tokens and a tag or branch can
# be moved to different code. When the design-gate action changes, move
# its two pins below to the commit that changed it.
- name: Evaluate the design gate
uses: cortexkit/subconscious/.github/actions/design-gate@0fad4b7008afa739e49858fe1083f194a6d00bb6
with:
mode: pull-request
owner-bots: ${{ inputs.owner-bots }}
github-token: ${{ secrets.GITHUB_TOKEN }}
check-token: ${{ secrets.GITHUB_TOKEN }}
# Marking a pull request ready with GITHUB_TOKEN does not start another
# workflow run, so this job re-runs the gate logic itself and publishes the
# `design-gate` check run on the PR head. Without that the PR would stay
# blocked until the author pushed a commit.
#
# It marks ready only the drafts the gate itself converted (their gate
# comment carries the `design-gate:converted` marker). A draft the author
# left as a draft on purpose is theirs; it gets the green check but stays a
# draft.
design-approved-sync:
if: github.event_name == 'issues' && github.event.label.name == 'design-approved'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read # fetch the gate action
issues: read # read the labelled issue
pull-requests: write # mark waiting drafts ready for review
checks: write # publish the design-gate check run on the PR head
steps:
# This arm runs on an `issues` event, where GITHUB_TOKEN can already do
# everything the gate needs, so both tokens are the same one here.
- name: Release pull requests waiting on this issue
uses: cortexkit/subconscious/.github/actions/design-gate@0fad4b7008afa739e49858fe1083f194a6d00bb6
with:
mode: issue-labeled
github-token: ${{ secrets.GITHUB_TOKEN }}
check-token: ${{ secrets.GITHUB_TOKEN }}