From 1270d9de86d378cb4e559899c01e593077f7a5c2 Mon Sep 17 00:00:00 2001 From: EHotwagner Date: Sat, 24 Oct 2026 20:21:52 +0200 Subject: [PATCH] Take the org-wide publication writer lock in every write-capable publisher job (Refs FS-GG/.github#4753) ADR-0097 (FS-GG/.github#4858, c799a9f9): GitHub concurrency groups are per repository, so all FS-GG publishers serialize through one ticketed lock over the public Actions job list. Each write-capable job here gets the pinned lock step first, a job name carrying [org-publication-writer], and its timeout raised by exactly the bounded wait so its own working budget is unchanged. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/release-skills.yml | 9 ++++-- .github/workflows/release-wasm.yml | 48 ++++++++++++++++++++++++---- .github/workflows/release.yml | 16 ++++++++-- 3 files changed, 62 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release-skills.yml b/.github/workflows/release-skills.yml index 54242c06..bafc6dba 100644 --- a/.github/workflows/release-skills.yml +++ b/.github/workflows/release-skills.yml @@ -139,9 +139,9 @@ jobs: echo "artifact-digest=${{ steps.custody.outputs.artifact-digest }}" publish: - name: Pack & publish FS.GG.Game.Skills + name: Pack & publish FS.GG.Game.Skills [org-publication-writer] runs-on: ubuntu-latest - timeout-minutes: 20 + timeout-minutes: 50 # Restrict to the canonical repo: fork tags/dispatch never run this (no fork secret exposure). if: github.repository == 'FS-GG/FS.GG.Game' && (github.event_name != 'workflow_dispatch' || inputs.readback_version == '') # Least-privilege: only this job gains packages: write, and the org-feed push uses the run-scoped @@ -152,6 +152,11 @@ jobs: packages: write id-token: write steps: + - name: Acquire org publication writer lock + uses: FS-GG/.github/.github/actions/publication-writer-lock@c799a9f9b21d52eb9766cf0950722e3e55145d39 # ADR-0097 + with: + max-wait-minutes: 30 + timeout-minutes: 32 - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 diff --git a/.github/workflows/release-wasm.yml b/.github/workflows/release-wasm.yml index b0306bf0..dd6720f2 100644 --- a/.github/workflows/release-wasm.yml +++ b/.github/workflows/release-wasm.yml @@ -65,10 +65,11 @@ jobs: bash tests/release/wasm/test-release-wasm.sh --source-only admission: + name: admission [org-publication-writer] needs: [preflight] if: inputs.mode == 'promote' || inputs.mode == 'recovery' || inputs.mode == 'readback' runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 45 permissions: actions: read checks: read @@ -76,6 +77,11 @@ jobs: packages: write id-token: write steps: + - name: Acquire org publication writer lock + uses: FS-GG/.github/.github/actions/publication-writer-lock@c799a9f9b21d52eb9766cf0950722e3e55145d39 # ADR-0097 + with: + max-wait-minutes: 30 + timeout-minutes: 32 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: fetch-depth: 0 @@ -109,16 +115,22 @@ jobs: retention-days: 90 begin: + name: begin [org-publication-writer] needs: [admission] if: inputs.mode == 'promote' || inputs.mode == 'recovery' runs-on: ubuntu-latest - timeout-minutes: 45 + timeout-minutes: 75 permissions: actions: read checks: read contents: write packages: write steps: + - name: Acquire org publication writer lock + uses: FS-GG/.github/.github/actions/publication-writer-lock@c799a9f9b21d52eb9766cf0950722e3e55145d39 # ADR-0097 + with: + max-wait-minutes: 30 + timeout-minutes: 32 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: fetch-depth: 0 @@ -149,16 +161,22 @@ jobs: retention-days: 90 org: + name: org [org-publication-writer] needs: [begin] if: inputs.mode == 'promote' || inputs.mode == 'recovery' runs-on: ubuntu-latest - timeout-minutes: 45 + timeout-minutes: 75 permissions: actions: read checks: read contents: write packages: write steps: + - name: Acquire org publication writer lock + uses: FS-GG/.github/.github/actions/publication-writer-lock@c799a9f9b21d52eb9766cf0950722e3e55145d39 # ADR-0097 + with: + max-wait-minutes: 30 + timeout-minutes: 32 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: fetch-depth: 0 @@ -188,10 +206,11 @@ jobs: retention-days: 90 public: + name: public [org-publication-writer] needs: [org] if: inputs.mode == 'promote' || inputs.mode == 'recovery' runs-on: ubuntu-latest - timeout-minutes: 45 + timeout-minutes: 75 permissions: actions: read checks: read @@ -199,6 +218,11 @@ jobs: packages: write id-token: write steps: + - name: Acquire org publication writer lock + uses: FS-GG/.github/.github/actions/publication-writer-lock@c799a9f9b21d52eb9766cf0950722e3e55145d39 # ADR-0097 + with: + max-wait-minutes: 30 + timeout-minutes: 32 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: fetch-depth: 0 @@ -233,16 +257,22 @@ jobs: retention-days: 90 assets: + name: assets [org-publication-writer] needs: [public] if: inputs.mode == 'promote' || inputs.mode == 'recovery' runs-on: ubuntu-latest - timeout-minutes: 45 + timeout-minutes: 75 permissions: actions: read checks: read contents: write packages: read steps: + - name: Acquire org publication writer lock + uses: FS-GG/.github/.github/actions/publication-writer-lock@c799a9f9b21d52eb9766cf0950722e3e55145d39 # ADR-0097 + with: + max-wait-minutes: 30 + timeout-minutes: 32 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: fetch-depth: 0 @@ -272,16 +302,22 @@ jobs: retention-days: 90 complete: + name: complete [org-publication-writer] needs: [org, public, assets] if: inputs.mode == 'promote' || inputs.mode == 'recovery' runs-on: ubuntu-latest - timeout-minutes: 45 + timeout-minutes: 75 permissions: actions: read checks: read contents: write packages: read steps: + - name: Acquire org publication writer lock + uses: FS-GG/.github/.github/actions/publication-writer-lock@c799a9f9b21d52eb9766cf0950722e3e55145d39 # ADR-0097 + with: + max-wait-minutes: 30 + timeout-minutes: 32 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: fetch-depth: 0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ede947e2..decd64a2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -84,7 +84,7 @@ jobs: run: dotnet test FS.GG.Game.slnx -c Debug --no-restore publish-packages: - name: Publish FS.GG.Game.* (org feed + nuget.org, release-only) + name: Publish FS.GG.Game.* (org feed + nuget.org, release-only) [org-publication-writer] runs-on: ubuntu-latest if: github.repository == 'FS-GG/FS.GG.Game' && (github.event_name != 'release' || startsWith(github.event.release.tag_name, 'v')) && (github.event_name != 'workflow_dispatch' || inputs.admission_only != true) needs: [verify] @@ -94,6 +94,11 @@ jobs: packages: write id-token: write # ADR-0013: mint the GitHub OIDC token for nuget.org Trusted Publishing steps: + - name: Acquire org publication writer lock + uses: FS-GG/.github/.github/actions/publication-writer-lock@c799a9f9b21d52eb9766cf0950722e3e55145d39 # ADR-0097 + with: + max-wait-minutes: 30 + timeout-minutes: 32 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: ref: ${{ github.event_name == 'workflow_dispatch' && inputs.version != '' && format('refs/tags/v{0}', inputs.version) || github.ref }} @@ -339,15 +344,20 @@ jobs: retention-days: 90 admission: - name: Observe registry and OIDC admission (no publication) + name: Observe registry and OIDC admission (no publication) [org-publication-writer] if: github.repository == 'FS-GG/FS.GG.Game' && github.event_name == 'workflow_dispatch' && inputs.admission_only == true runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 35 permissions: contents: read packages: write # Match the publisher principal; this job makes only registry GET requests. id-token: write steps: + - name: Acquire org publication writer lock + uses: FS-GG/.github/.github/actions/publication-writer-lock@c799a9f9b21d52eb9766cf0950722e3e55145d39 # ADR-0097 + with: + max-wait-minutes: 30 + timeout-minutes: 32 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Guard isolated observation mode shell: bash