diff --git a/.github/workflows/export-docs.yml b/.github/workflows/export-docs.yml index 044a5fe..bf68bec 100644 --- a/.github/workflows/export-docs.yml +++ b/.github/workflows/export-docs.yml @@ -4,7 +4,7 @@ # This repo is the origin of truth for shared docs content — the generated # files downstream (docs/generating-coverage-reports.md, docs/INSTALLATION.md) # must never be edited directly. See README.md § Docs export pipeline for the -# COVERAGE_TRACKER_SYNC_TOKEN setup. +# docs-sync GitHub App setup (DOCS_SYNC_APP_ID / DOCS_SYNC_APP_PRIVATE_KEY). name: Export docs on: @@ -17,7 +17,6 @@ on: workflow_dispatch: permissions: - id-token: write contents: write pull-requests: write @@ -25,6 +24,20 @@ jobs: export: runs-on: ubuntu-latest steps: + # Mint an installation token for the docs-sync GitHub App. A bot token is + # required for commit signing: create-pull-request's sign-commits builds + # commits via the GitHub API, which only signs them (as the app bot) when + # authenticated with a GitHub App token — a PAT leaves them unsigned. + # owner/repositories are mandatory to scope the token to the cross-org target repo. + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.DOCS_SYNC_APP_ID }} + private-key: ${{ secrets.DOCS_SYNC_APP_PRIVATE_KEY }} + owner: CoverageTracker + repositories: coverage-tracker + - name: Checkout coveragetracker.dev uses: actions/checkout@v4 @@ -32,7 +45,7 @@ jobs: uses: actions/checkout@v4 with: repository: CoverageTracker/coverage-tracker - token: ${{ secrets.COVERAGE_TRACKER_SYNC_TOKEN }} + token: ${{ steps.app-token.outputs.token }} path: target-repo - name: Setup Node @@ -40,9 +53,6 @@ jobs: with: node-version: 22 - - name: Setup Gitsign - uses: chainguard-dev/actions/setup-gitsign@f0be69916b439d0fcced2451b23d0f27cd46d545 # v1.6.26 - - name: Export docs run: node --experimental-strip-types scripts/export-docs.ts target-repo @@ -54,7 +64,7 @@ jobs: path: target-repo branch: docs-sync sign-commits: true - token: ${{ secrets.COVERAGE_TRACKER_SYNC_TOKEN }} + token: ${{ steps.app-token.outputs.token }} commit-message: 'docs: sync generated docs from coveragetracker.dev' title: 'docs: sync generated docs from coveragetracker.dev' body: | diff --git a/README.md b/README.md index cff78c1..3187fdf 100644 --- a/README.md +++ b/README.md @@ -98,23 +98,33 @@ Run it locally against a sibling checkout: pnpm export-docs ../coverage-tracker ``` -### Provisioning the sync token - -The workflow authenticates with a fine-grained PAT stored as the repo secret -`COVERAGE_TRACKER_SYNC_TOKEN`. The product GitHub App's permissions are deliberately -**not** widened for this docs plumbing. To (re)provision the token — after expiry, a new -fork, or a maintainer change: - -1. GitHub → **Settings → Developer settings → Personal access tokens → Fine-grained - tokens → Generate new token** -2. Resource owner: the **CoverageTracker** org; Repository access: **Only select - repositories** → `coverage-tracker` -3. Repository permissions: **Contents → Read and write**, **Pull requests → Read and - write**; everything else: No access -4. Set an expiration and note it somewhere you'll see it — when the token lapses, the - workflow fails with an auth error on the checkout or PR step -5. Save the token in **this repo** as the Actions secret `COVERAGE_TRACKER_SYNC_TOKEN` - (Settings → Secrets and variables → Actions) +### Provisioning the sync credentials + +The target repo requires **signed commits** on PRs, so the workflow authenticates with a +dedicated **GitHub App** installation token rather than a PAT. This is deliberate: +`create-pull-request`'s `sign-commits` builds commits through the GitHub API, and the API +only signs them (as the app bot, shown *Verified*) when the token is a bot token — a PAT +leaves the commits **unsigned** and branch protection rejects the PR. The product GitHub +App's permissions are deliberately **not** widened for this docs plumbing, so this uses a +separate minimal App. + +To (re)provision — a new fork, a rotated key, or a maintainer change: + +1. **CoverageTracker** org → **Settings → Developer settings → GitHub Apps → New GitHub + App**. Name it e.g. `coveragetracker-docs-sync`, give any valid homepage URL, and + uncheck **Webhook → Active**. +2. Repository permissions: **Contents → Read and write**, **Pull requests → Read and + write**; everything else: No access. Create the App. +3. App settings → **Private keys → Generate a private key** and download the `.pem`. +4. **Install** the App on the org, scoped to **Only select repositories → `coverage-tracker`** + (installation is required — creating the App alone does nothing). +5. In **this repo** → Settings → Secrets and variables → Actions, add two secrets: + - `DOCS_SYNC_APP_ID` — the App's numeric **App ID** + - `DOCS_SYNC_APP_PRIVATE_KEY` — the full contents of the downloaded `.pem` + +The App has no expiry to track; installation tokens are minted per run by +`actions/create-github-app-token`. If commits ever land **Unverified**, check that the App +is still installed on `coverage-tracker`. ## Theming