-
Notifications
You must be signed in to change notification settings - Fork 0
205 lines (185 loc) · 7.41 KB
/
Copy pathpr-fast-ci.yml
File metadata and controls
205 lines (185 loc) · 7.41 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
name: PR Fast CI
on:
pull_request:
types: [opened, edited, synchronize, reopened, ready_for_review]
pull_request_review:
types: [submitted, edited, dismissed]
concurrency:
group: pr-fast-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: read
env:
NODE_VERSION: '20'
PYTHON_VERSION: '3.12'
defaults:
run:
shell: bash
jobs:
changes:
name: Detect Relevant Changes
runs-on: ['self-hosted', 'linux', 'shell-only', 'public']
outputs:
app: ${{ steps.filter.outputs.app }}
ci: ${{ steps.filter.outputs.ci }}
steps:
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4
id: filter
with:
filters: |
app:
- 'project.bootstrap.yaml'
- 'AGENTS.md'
- 'CONTRIBUTING.md'
- '.github/PULL_REQUEST_TEMPLATE.md'
- '.githooks/**'
- '.github/workflows/**'
- 'scripts/**'
- 'docs/bootstrap/**'
- 'README.md'
- 'docs/**'
ci:
- 'project.bootstrap.yaml'
- 'AGENTS.md'
- 'CONTRIBUTING.md'
- '.github/PULL_REQUEST_TEMPLATE.md'
- '.githooks/**'
- '.github/workflows/**'
- 'scripts/**'
- 'docs/bootstrap/**'
- '.env.example'
- 'CODEOWNERS'
fast-checks:
name: Fast Checks
runs-on: ['self-hosted', 'linux', 'shell-only', 'public']
timeout-minutes: 15
needs: changes
if: >-
github.event.pull_request.draft == false &&
(needs.changes.outputs.app == 'true' || needs.changes.outputs.ci == 'true')
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Run fast checks
run: bash scripts/ci/run-fast-checks.sh
validate-pr-description:
name: Validate PR Description
runs-on: ['self-hosted', 'linux', 'shell-only', 'public']
timeout-minutes: 5
if: github.event.pull_request.draft == false && github.event.pull_request.user.login != 'dependabot[bot]'
env:
PR_BODY: ${{ github.event.pull_request.body }}
steps:
- name: Require generated PR template content
run: |
failed=0
require_line() {
local line="$1"
if ! grep -Fqx "$line" <<<"$PR_BODY"; then
echo "Missing required PR section: $line"
failed=1
fi
}
require_line "## Summary"
require_line "## Governing Issue"
require_line "## Validation"
require_line "## Bootstrap Governance"
require_line "## Merge Automation"
require_line "## Notes"
if grep -Eiq 'Closes #$|#<issue-number>|what changed|why it changed|notable tradeoffs|migration or rollout notes|follow-up work if any' <<<"$PR_BODY"; then
echo "PR body still contains template placeholder text."
failed=1
fi
if ! grep -Eiq '(^|[[:space:]-])(((close[sd]?|fix(e[sd])?|resolve[sd]?|refs?|part[[:space:]]+of)[[:space:]]+)?(#|[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+#|https://github\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/issues/)[0-9]+|no issue is linked|no linked issue|without a linked issue|no governing issue)' <<<"$PR_BODY"; then
echo "PR body must close/link an issue or explicitly explain why no issue is linked."
failed=1
fi
if ! grep -Eiq '(^|[[:space:]-])(\[[xX]\]|not run|not applicable|n/a)' <<<"$PR_BODY"; then
echo "PR body must include validation evidence, a checked validation item, or a reason validation was not run."
failed=1
fi
auto_merge_evidence="$(grep -Eiv '^[[:space:]]*-[[:space:]]+\[[[:space:]]\][[:space:]]' <<<"$PR_BODY" || true)"
if ! grep -Eiq 'auto-merge (is )?(enabled|armed)|enabled auto-merge|gh pr merge --auto|auto_merge|auto merge enabled|auto-merge (is )?(unavailable|unsafe|not available|not safe)|plan-limit|fallback merge-readiness' <<<"$auto_merge_evidence"; then
echo "PR body must state that the PR author enabled auto-merge, or explain why auto-merge is unavailable/unsafe."
failed=1
fi
exit "$failed"
validate-secrets:
name: Validate Secrets
runs-on: ['self-hosted', 'linux', 'shell-only', 'public']
timeout-minutes: 10
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Scan repository for secret patterns
run: bash scripts/check-detect-secrets.sh --all-files
validate-pr-governance:
name: Validate PR Governance
runs-on: ['self-hosted', 'linux', 'shell-only', 'public']
timeout-minutes: 5
if: github.event.pull_request.draft == false && github.event.pull_request.user.login != 'dependabot[bot]'
env:
PR_TITLE: ${{ github.event.pull_request.title }}
PR_BODY: ${{ github.event.pull_request.body }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PR_CREATED_AT: ${{ github.event.pull_request.created_at }}
PR_GOVERNANCE_ENFORCE_AFTER: '2026-07-13T23:00:00Z'
PR_FILES_URL: ${{ github.event.pull_request.url }}/files
PR_COMMITS_URL: ${{ github.event.pull_request.commits_url }}
PR_REVIEWS_URL: ${{ github.event.pull_request.url }}/reviews
GITHUB_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Validate title, DCO, size, ADR, and reviewer evidence
run: bash scripts/ci/check-pr-governance.sh
validate-action-pins:
name: Validate Action Pins
runs-on: ['self-hosted', 'linux', 'shell-only', 'public']
timeout-minutes: 5
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Require immutable third-party action pins
run: bash scripts/ci/check-action-pins.sh
ci-gate:
name: CI Gate
runs-on: ['self-hosted', 'linux', 'shell-only', 'public']
if: always()
needs:
- changes
- fast-checks
- validate-pr-description
- validate-secrets
- validate-pr-governance
- validate-action-pins
steps:
- name: Check required PR jobs
env:
RESULTS: >-
changes=${{ needs.changes.result }}
fast-checks=${{ needs.fast-checks.result }}
validate-pr-description=${{ needs.validate-pr-description.result }}
validate-secrets=${{ needs.validate-secrets.result }}
validate-pr-governance=${{ needs.validate-pr-governance.result }}
validate-action-pins=${{ needs.validate-action-pins.result }}
run: |
failed=0
for entry in $RESULTS; do
job="${entry%%=*}"
status="${entry##*=}"
if [[ "$status" == "success" || "$status" == "skipped" ]]; then
echo "OK $job => $status"
else
echo "FAIL $job => $status"
failed=1
fi
done
exit "$failed"