-
-
Notifications
You must be signed in to change notification settings - Fork 0
174 lines (158 loc) · 7.75 KB
/
Copy pathrelease-gating.yml
File metadata and controls
174 lines (158 loc) · 7.75 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
name: release-gating action
# When culture content enters culture/release, stage its release gating
# data -- the data/countries.json registry entry -- on main BEFORE the
# culture/release -> main promotion.
#
# For every culture present in culture/release, generate a skeleton
# countries.json entry (scripts/generate_country_entry.py); if that
# changes the registry, open a governance/release-gating-* PR against
# main. A human completes the TODO fields (anchor.iso, name_source) --
# the release audit -- and the blocking gate (release-gating-gate.yml)
# holds the promotion until this PR merges. See issue #257.
#
# Branch lane: data/countries.json is in GOVERNANCE_FILES
# (tests/branch_scope.py) because the registry schema ships with the
# release pipeline. The pr-gate rejects governance-file edits on
# chore/* heads, so the workflow opens its auto-PR on a governance/*
# branch and the alignment cross-check
# (tests/test_culture_completeness_alignment.py) skips on that head.
#
# Scope: this stages the COUNTRY REGISTRY only. Persona names continue to
# sync via sync_name_register.yml -- name_register.json is governance-lane
# and cannot share this PR; the blocking gate covers the registry only.
# Gating name_register would be a separate extension.
on:
push:
branches:
- culture/release
# Concurrency: when multiple culture PRs land on culture/release in quick
# succession, each push fires its own run. Without a guard, 7 parallel
# culture PRs => up to 7 governance/release-gating-* PRs with overlapping
# countries.json edits. The detect step computes the FULL main <-> release
# delta, so any single run is naturally cumulative -- only the latest
# matters. Cancel in-progress runs and let the newest one produce the
# single PR that covers every culture currently outstanding.
concurrency:
group: release-gating-cultures
cancel-in-progress: true
permissions:
contents: write
pull-requests: write
jobs:
stage-registry:
runs-on: ubuntu-latest
steps:
- name: Checkout main
# PR target tree: peter-evans bases the chore branch off this
# checkout's HEAD, so the resulting PR diff is main + the
# countries.json change only.
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
- name: Checkout culture/release into ./release
# Source-of-truth tree for the generator. The reason this whole
# workflow exists is that the culture's folder
# (regions/<region>/<parent>?/<slug>/) does not yet exist on
# main -- it's still in culture/release waiting for the
# promotion. scripts/generate_country_entry.py reads that folder
# to derive `language` from hofstede_bag.yaml and to confirm the
# sovereign-vs-sub layout, so it needs the release tree mounted
# somewhere on disk. Sibling worktree keeps the main checkout
# untouched for peter-evans below.
uses: actions/checkout@v4
with:
ref: culture/release
fetch-depth: 0
path: release
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install pyyaml
- name: Detect cultures present in culture/release
id: detect
# Sub-national-aware slug extraction (see
# scripts/extract_culture_slugs.py): depth-3 sovereign-state
# and depth-4 sub-national share one Python helper that
# release-gating-gate.yml also uses, so the two workflows
# can't drift on the depth contract.
run: |
git fetch origin culture/release
SLUGS=$(git diff --name-only main origin/culture/release \
| python3 scripts/extract_culture_slugs.py \
| tr '\n' ' ' | xargs)
echo "slugs=$SLUGS" >> "$GITHUB_OUTPUT"
echo "first=$(echo "$SLUGS" | awk '{print $1}')" >> "$GITHUB_OUTPUT"
echo "Cultures in culture/release: ${SLUGS:-<none>}"
- name: Generate skeleton registry entries
# Run from release/ so the script's REPO_ROOT resolves to the
# culture/release tree where the slug's folder lives. The
# script reads and writes release/data/countries.json; the next
# step copies the result back into the main checkout for the
# PR.
#
# Invariant relied on: data/countries.json on culture/release
# equals main's. Registry edits land governance-side via this
# same automation (governance/release-gating-registry -> main)
# and then sync back into culture/release through the standard
# sync/release-from-main-* PRs -- never a direct write to
# culture/release. If the invariant ever drifts, the copy step
# would silently revert release-side changes; the diff step
# below would still surface that as a real change against main.
if: steps.detect.outputs.slugs != ''
working-directory: release
run: python scripts/generate_country_entry.py ${{ steps.detect.outputs.slugs }}
- name: Copy registry change back to main worktree
# peter-evans below commits whatever the main checkout's working
# tree carries; bring the countries.json delta there.
if: steps.detect.outputs.slugs != ''
run: cp release/data/countries.json data/countries.json
- name: Detect registry change
id: diff
run: |
if git diff --quiet data/countries.json; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Open gating-data PR
if: steps.diff.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
# Stable branch name -- the same PR is updated on each
# culture/release push instead of cloning a new one per commit
# SHA. peter-evans/create-pull-request@v6 3-way-merges user
# edits (TODO fills) on top of the regenerated skeleton, so
# filled-in fields are preserved across re-runs. The generator
# (scripts/generate_country_entry.py) is idempotent: a slug
# already in countries.json is left untouched, so once a
# culture's entry merges to main, subsequent runs ignore it.
#
# governance/ lane (not chore/): data/countries.json is in
# GOVERNANCE_FILES, and the pr-gate's "Diff within branch
# scope" check rejects governance-file edits on chore/* heads.
# See the top-of-file comment for the routing.
branch: governance/release-gating-registry
base: main
# Restrict the commit to the registry file. The sibling
# `release/` checkout above is a separate git worktree; a
# blanket `git add .` would capture its .git as a submodule
# gitlink and the PR would carry a bogus subproject entry.
# add-paths is the supported way to scope what peter-evans
# commits.
add-paths: data/countries.json
commit-message: 'chore: stage release gating data for ${{ steps.detect.outputs.first }}'
title: 'chore: release gating data - ${{ steps.detect.outputs.first }}'
body: |
Automated release gating data -- generated because culture
content for an unregistered culture is present in `culture/release`.
Skeleton `data/countries.json` entries were added. **Complete every
`TODO` field** (`anchor.iso`, `name_source`) before merging -- that
completion is the release audit.
The blocking gate (`release-gating-gate.yml`) holds the
`culture/release -> main` promotion until this PR is merged.
See issue #257.
labels: chore
draft: false