Skip to content

Commit 7740ea8

Browse files
Merge branch 'main' into hephaestus-avatar-brand-refresh
2 parents 489775e + 75cd88e commit 7740ea8

8 files changed

Lines changed: 469 additions & 541 deletions

File tree

.github/workflows/ci-compose-validate.yml

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,14 @@
11
name: Validate Compose
22

3-
# The self-hosted stack (docker/self-host/) composes the reference deployment's
4-
# service definitions via `include`, so a change to docker/compose.*.yaml can
5-
# break a stranger's install without touching a single self-host file. Rendering
6-
# both stacks here turns interpolation- and merge-level breakage into a red check
7-
# instead of a bad first boot. (Semantic breakage — a renamed service silently
8-
# joining the stack, an inherited runtime bug — still needs a real boot.)
9-
#
10-
# Runs on every PR (not just docker/ changes) so it can be a required check,
11-
# matching how verify-changesets.yml is wired. It is cheap: only `docker compose
12-
# config`, no image pulls.
3+
# Shared Compose files can break either the reference or self-hosted stack, so
4+
# render both configurations.
135

146
on:
15-
pull_request:
16-
push:
17-
branches: [main]
7+
workflow_call:
188

199
permissions:
2010
contents: read
2111

22-
concurrency:
23-
group: validate-compose-${{ github.event.pull_request.number || github.run_id }}
24-
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
25-
2612
jobs:
2713
validate:
2814
name: "Render compose stacks"
@@ -37,9 +23,7 @@ jobs:
3723
working-directory: docker/self-host
3824
run: |
3925
set -euo pipefail
40-
# Fill the required values the way an operator would; `docker compose
41-
# config` fails on any variable the stack requires but .env.example
42-
# does not carry, which is exactly the drift we want to catch.
26+
# Supply required operator values; missing defaults must still fail rendering.
4327
cp .env.example .env
4428
sed -i \
4529
-e 's|^APP_HOSTNAME=$|APP_HOSTNAME=hephaestus.example.com|' \
@@ -66,8 +50,7 @@ jobs:
6650
working-directory: docker/self-host
6751
run: |
6852
set -euo pipefail
69-
# `config` only warns about variables missing from .env; a warning here
70-
# means .env.example has fallen behind the stack it renders.
53+
# `config` warns rather than fails when .env omits a referenced variable.
7154
if docker compose config 2>&1 >/dev/null | grep "variable is not set"; then
7255
echo "::error::docker/self-host/.env.example is missing variables the stack references (see warnings above)"
7356
exit 1
@@ -86,9 +69,7 @@ jobs:
8669
grep -qx "$required" <<< "$services" || {
8770
echo "::error::'$required' is missing from the self-hosted stack"; exit 1; }
8871
done
89-
# Compose 2.21-2.23 parse `!override` but silently ignore it, which would
90-
# publish the reference's dashboard port and keep the maintainers' ACME
91-
# email. Assert the merged result rather than trust the runner's version.
72+
# Some Compose releases accept `!override` without applying it.
9273
rendered=$(docker compose config)
9374
for required in \
9475
'entrypoints.https.http.middlewares=security-headers@docker' \

.github/workflows/cicd.yml

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ jobs:
3434
ci-config: ${{ steps.filter.outputs.ci-config }}
3535
docker-config: ${{ steps.filter.outputs.docker-config }}
3636
pmd-canary: ${{ steps.filter.outputs.pmd-canary }}
37+
version-bump: ${{ steps.version_bump.outputs.changed }}
3738
any-code: ${{ steps.filter.outputs.webapp == 'true' || steps.filter.outputs.application-server == 'true' || steps.filter.outputs.tooling == 'true' || steps.filter.outputs.agent-images == 'true' || steps.filter.outputs.postgres-image == 'true' }}
3839
should_skip: ${{ steps.skip_check.outputs.should_skip }}
3940
timeout-minutes: 5
@@ -48,6 +49,16 @@ jobs:
4849
with:
4950
do_not_skip: '["workflow_dispatch", "push", "merge_group"]'
5051

52+
- name: Detect version bump
53+
id: version_bump
54+
if: github.event_name == 'push'
55+
run: |
56+
current=$(jq -r .version package.json)
57+
previous=$(git show HEAD^:package.json | jq -r .version)
58+
changed=false
59+
[ "$current" = "$previous" ] || changed=true
60+
echo "changed=$changed" >> "$GITHUB_OUTPUT"
61+
5162
- uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
5263
id: filter
5364
with:
@@ -183,6 +194,7 @@ jobs:
183194
runs-on: ubuntu-latest
184195
needs: [detect-changes]
185196
if: |
197+
(github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true') &&
186198
needs.detect-changes.outputs.should_skip != 'true' && (
187199
needs.detect-changes.outputs.ci-config == 'true' ||
188200
github.event_name != 'pull_request'
@@ -207,6 +219,8 @@ jobs:
207219
name: "Zizmor"
208220
timeout-minutes: 10
209221
runs-on: ubuntu-latest
222+
needs: [detect-changes]
223+
if: github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true'
210224
permissions:
211225
contents: read
212226
security-events: write
@@ -230,6 +244,7 @@ jobs:
230244
uses: ./.github/workflows/ci-quality-gates.yml
231245
needs: [detect-changes]
232246
if: |
247+
(github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true') &&
233248
needs.detect-changes.outputs.should_skip != 'true' && (
234249
needs.detect-changes.outputs.any-code == 'true' ||
235250
needs.detect-changes.outputs.ci-config == 'true' ||
@@ -251,6 +266,7 @@ jobs:
251266
uses: ./.github/workflows/ci-security-scan.yml
252267
needs: [detect-changes]
253268
if: |
269+
(github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true') &&
254270
needs.detect-changes.outputs.should_skip != 'true' && (
255271
needs.detect-changes.outputs.any-code == 'true' ||
256272
needs.detect-changes.outputs.ci-config == 'true' ||
@@ -266,6 +282,7 @@ jobs:
266282
uses: ./.github/workflows/ci-tests.yml
267283
needs: [detect-changes]
268284
if: |
285+
(github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true') &&
269286
needs.detect-changes.outputs.should_skip != 'true' && (
270287
needs.detect-changes.outputs.any-code == 'true' ||
271288
needs.detect-changes.outputs.ci-config == 'true' ||
@@ -292,6 +309,13 @@ jobs:
292309
permissions:
293310
contents: read
294311

312+
Compose:
313+
needs: [detect-changes]
314+
if: github.event_name != 'push' || needs.detect-changes.outputs.version-bump == 'true'
315+
uses: ./.github/workflows/ci-compose-validate.yml
316+
permissions:
317+
contents: read
318+
295319
Docker:
296320
uses: ./.github/workflows/ci-docker-build.yml
297321
secrets:
@@ -331,7 +355,7 @@ jobs:
331355
permissions:
332356
actions: read
333357
statuses: write
334-
needs: [detect-changes, workflow-lint, zizmor, Quality, Security, Test, Changesets, Docker]
358+
needs: [detect-changes, workflow-lint, zizmor, Quality, Security, Test, Changesets, Compose, Docker]
335359
if: always()
336360
steps:
337361
- name: Generate workflow timeline
@@ -388,6 +412,7 @@ jobs:
388412
echo "Security: ${{ needs.Security.result }}"
389413
echo "Test: ${{ needs.Test.result }}"
390414
echo "Changesets: ${{ needs.Changesets.result }}"
415+
echo "Compose: ${{ needs.Compose.result }}"
391416
echo "Docker: ${{ needs.Docker.result }}"
392417
393418
if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then
@@ -437,6 +462,7 @@ jobs:
437462
echo "| Quality | $(result_to_emoji '${{ needs.Quality.result }}') |" >> $GITHUB_STEP_SUMMARY
438463
echo "| Test | $(result_to_emoji '${{ needs.Test.result }}') |" >> $GITHUB_STEP_SUMMARY
439464
echo "| Changesets | $(result_to_emoji '${{ needs.Changesets.result }}') |" >> $GITHUB_STEP_SUMMARY
465+
echo "| Compose | $(result_to_emoji '${{ needs.Compose.result }}') |" >> $GITHUB_STEP_SUMMARY
440466
echo "| Security | $(result_to_emoji '${{ needs.Security.result }}') |" >> $GITHUB_STEP_SUMMARY
441467
echo "| Docker | $(result_to_emoji '${{ needs.Docker.result }}') |" >> $GITHUB_STEP_SUMMARY
442468
echo "" >> $GITHUB_STEP_SUMMARY

.github/workflows/review-policy.yml

Lines changed: 19 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,72 +4,44 @@ on:
44
pull_request_target: # zizmor: ignore[dangerous-triggers] API reads only; no pull-request code is executed.
55
branches: [main]
66
types: [opened, reopened, synchronize, ready_for_review]
7-
pull_request_review:
8-
types: [submitted, dismissed]
9-
merge_group:
107

118
permissions: {}
129

1310
concurrency:
14-
# Never cancel: runs in a group are serialised, so the newest event's verdict is the one that
15-
# lands, and a cancelled run publishes nothing rather than a half-written state.
16-
group: review-policy-${{ github.event.pull_request.number || github.ref }}
11+
# Never cancel: runs in a group are serialised, so the newest event's decision is the one that
12+
# lands, and a cancelled run leaves an approval unsubmitted rather than half-written.
13+
group: review-policy-${{ github.event.pull_request.number }}
1714
cancel-in-progress: false
1815

1916
jobs:
20-
# This job is *not* the required context. It publishes one — a check run named `review-policy`,
21-
# which the `main` ruleset requires and binds to the GitHub Actions integration that a
22-
# `GITHUB_TOKEN`-authenticated check run is attributed to. Reporting the verdict separately is
23-
# what lets an unreviewed pull request sit pending instead of red: a job can only pass or fail,
24-
# and a failing job made a healthy pull request awaiting its first review look broken on the pull
25-
# request list. Renaming this job back to `review-policy` would put two check runs of that name
26-
# on the same commit and make which one the ruleset reads a race.
27-
publish:
28-
name: Publish review-policy status
17+
# This job is *not* a required status check, and must never become one. The `main` ruleset requires
18+
# one native approving review; all this job does is supply that approval for the authors listed in
19+
# `REVIEW_POLICY_MAINTAINERS`. A pull request by anyone else gets no approval here and sits in
20+
# GitHub's own "Review required" state, which blocks merging, merge-queue entry and auto-merge.
21+
#
22+
# `synchronize` is load-bearing: the ruleset dismisses stale approvals on push, so every push has
23+
# to earn a fresh one.
24+
approve:
25+
name: Approve a maintainer's pull request
2926
runs-on: ubuntu-latest
3027
timeout-minutes: 5
3128
permissions:
32-
checks: write
3329
contents: read
34-
pull-requests: read
30+
pull-requests: write
3531
steps:
36-
# The queue re-reports every required context against the projected commit, and the policy was
37-
# already decided on the pull request that entered the queue. Written out here rather than
38-
# routed through the evaluator so the queue's path needs no checkout and no import: a merge
39-
# group that never receives this context sits until the queue's timeout drops it.
40-
- name: Pass the merge group
41-
if: github.event_name == 'merge_group'
42-
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
43-
with:
44-
script: |
45-
await github.rest.checks.create({
46-
...context.repo,
47-
// Kept equal to CHECK_NAME in scripts/review-policy.ts; a test asserts they agree.
48-
name: 'review-policy',
49-
head_sha: context.payload.merge_group.head_sha,
50-
status: 'completed',
51-
conclusion: 'success',
52-
output: {
53-
title: 'Satisfied before this merge group was created',
54-
summary:
55-
'The review policy is decided on the pull request; entering the merge queue ' +
56-
'already required it to pass.',
57-
},
58-
});
59-
60-
# The default branch, never `github.sha`: that is the pull request's merge commit on
61-
# `pull_request_review`, which contains pull-request code. The base branch's copy of the
62-
# policy is the authoritative one.
32+
# The default branch, never `github.sha` or the pull request's head: the base branch's copy of
33+
# the policy is the authoritative one, and nothing from the pull request is fetched or run.
6334
- name: Load the trusted policy evaluator
64-
if: github.event_name != 'merge_group'
6535
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
6636
with:
6737
ref: ${{ github.event.repository.default_branch }}
6838
persist-credentials: false
6939
sparse-checkout: scripts
7040

71-
- name: Publish the review-policy check run
72-
if: github.event_name != 'merge_group'
41+
# The review is attributed to `github-actions[bot]`, which counts toward the ruleset's
42+
# `required_approving_review_count`. That needs the organisation's "Allow GitHub Actions to
43+
# create and approve pull requests" setting left on.
44+
- name: Approve when the author is a listed maintainer
7345
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
7446
env:
7547
MAINTAINERS: ${{ vars.REVIEW_POLICY_MAINTAINERS }}

0 commit comments

Comments
 (0)