Merge pull request #549 from ChBrain/culture/schleswig_holstein #81
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |