PAT Rotation and Diagnostics ============================ The ``GH_PAT`` secret is a GitHub Personal Access Token that allows the CI/CD pipeline to read build state and trigger cross-repository workflows. It must be present in every repository that participates in the pipeline. The ``ccat pat`` subcommands provide a single, safe interface for checking and rotating this secret across all ccatobs repositories. Why a PAT is needed ------------------- The ``orchestrate-deploy.yml`` workflow in ``system-integration`` dispatches builds to leaf repositories and reads their workflow status. Both operations require a token with permissions beyond the default ``GITHUB_TOKEN`` scope. The canonical list of repositories that need ``GH_PAT`` is maintained in ``ci/dependency-graph.yml``. Adding a new repo to that file is sufficient to include it in the next ``ccat pat rotate`` run. Generating a PAT with minimum permissions ------------------------------------------ Create a **fine-grained** Personal Access Token: 1. Go to **GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token**. 2. Set **Resource owner** to ``ccatobs`` (the organisation). 3. Set **Repository access** to *All repositories* (or select individual repos matching ``ci/dependency-graph.yml``). 4. Under **Permissions → Repository permissions**, grant: - **Actions**: Read-only (allows reading workflow run status) - **Secrets**: Read and write (allows ``ccat pat rotate`` to set the secret) 5. Set an expiry date and note it somewhere so you know when to rotate. .. note:: Classic PATs also work. The minimum scopes are ``repo`` and ``workflow``. Fine-grained tokens are preferred because their permissions are narrower and auditable per repository. Where the secret must be set ------------------------------ ``GH_PAT`` is read by ``orchestrate-deploy.yml`` in **ccatobs/system-integration**. However, leaf repositories also need the secret so their own triggered workflows can authenticate back to the orchestrator. The complete list of repositories is driven by ``ci/dependency-graph.yml``. Run ``ccat pat status`` at any time to see the current state. Checking PAT status -------------------- .. code-block:: console $ ccat pat status Prints a table for every repository in the dependency graph: .. code-block:: text ╭────────────────────────────────────┬────────────────┬──────────────────────────╮ │ Repository │ Secret present │ Last rotated │ ├────────────────────────────────────┼────────────────┼──────────────────────────┤ │ ccatobs/system-integration │ ✓ present │ 2026-02-21T22:17:45Z │ │ ccatobs/ops-db │ ✓ present │ 2026-02-21T22:17:46Z │ │ ccatobs/ops-db-api │ ✗ missing │ — │ │ ... │ ... │ ... │ ╰────────────────────────────────────┴────────────────┴──────────────────────────╯ Exit code is non-zero if any secret is missing, making it safe to call from CI. .. note:: The Secrets API returns only the ``updated_at`` timestamp. The token value itself is never exposed. To verify that a token is still valid, generate a new one and run ``ccat pat rotate`` — the old one is overwritten atomically. Rotating the PAT ----------------- Preview what would be updated (no secrets written): .. code-block:: console $ ccat pat rotate --dry-run Perform the rotation: .. code-block:: console $ ccat pat rotate Paste GH_PAT token: The command: 1. Prompts for the token with hidden input (not echoed to the terminal and not stored in shell history). 2. Iterates over every repository in ``ci/dependency-graph.yml`` plus ``system-integration``, calling ``gh secret set GH_PAT`` for each. 3. Stops on the first failure and reports which repositories were already updated. Re-running after fixing the error is safe — setting the same secret twice is idempotent. Requires the ``gh`` CLI to be authenticated as an organisation member with **Secrets: Read and write** access to all ccatobs repositories (i.e. the same account used to generate the PAT above, or an admin account). Recovery after partial failure -------------------------------- If ``ccat pat rotate`` stops mid-run, the output clearly lists which repositories were already updated. The remaining repositories still hold the old token. To recover: .. code-block:: console $ ccat pat rotate # re-run with the same new token The repositories that were already updated are set again (no-op), and the run continues from where it left off. Recommended rotation cadence ------------------------------ - **Fine-grained PATs**: rotate every 90 days or before expiry, whichever comes first. - **Classic PATs**: rotate every 60 days (shorter because the scope is broader). - After any team member with access to the token leaves the organisation. - Immediately upon any suspected exposure. Run ``ccat pat status`` after rotation to confirm all secrets show an updated ``Last rotated`` timestamp.