Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions .github/workflows/apply-repo-settings-reusable.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Reusable repo-settings + branch-policy self-heal — single source of truth for the org.
# Repo-level apply-repo-settings.yml stubs call this so the compliance self-heal runs
# from ONE script/workflow pair instead of per-repo forks (petry-projects/.github#984).
# Standard: https://github.com/petry-projects/.github/blob/main/standards/github-settings.md
# https://github.com/petry-projects/.github/blob/main/standards/ruleset-remediation-runbook.md
#
# Applies, to the CALLING repo:
# - merge settings, security_and_analysis, CodeQL default setup, labels, and
# check-suite auto-trigger preferences (scripts/apply-repo-settings.sh)
# - the branch-policy compliance rulesets pr-quality + code-quality
# (scripts/apply-rulesets.sh) — the recurring finding this workflow exists to
# fix (e.g. pr-quality's require_last_push_approval flipping to false).
#
# Credential: the check-suites/preferences endpoint and the ruleset admin calls are
# legacy — they accept ONLY a classic PAT (or GitHub App token) whose owner has
# repo-admin. Fine-grained PATs and GITHUB_TOKEN are rejected (403). The org secret
# GH_PAT_DON_PETRY holds that classic admin PAT; the preflight below fails loud,
# naming the secret, when it is empty/out-of-scope rather than dying obscurely deep
# in the script.
name: Apply repo settings (Reusable)

on:
workflow_call:
inputs:
dry_run:
description: "Preview changes without applying (sets DRY_RUN=true)"
type: boolean
required: false
default: false
checkout_ref:
description: >-
Ref of petry-projects/.github to check out for the scripts, so the
logic runs at the same version as this reusable. Callers pin their
ring's channel tag (mirrors dev-lead's agent_ref). Empty = default branch.
type: string
required: false
default: ""
secrets:
GH_PAT_DON_PETRY:
description: "Classic PAT with repo scope whose owner has admin on target repos (check-suites/rulesets APIs reject fine-grained PATs and GITHUB_TOKEN)"
required: false

permissions: {} # no default permissions; each job grants only what it needs

jobs:
apply:
name: Apply settings and rulesets
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
steps:
- name: Preflight — require the admin PAT
env:
GH_PAT_DON_PETRY: ${{ secrets.GH_PAT_DON_PETRY }}
run: |
set -euo pipefail
if [[ -z "${GH_PAT_DON_PETRY:-}" ]]; then
_msg="The GH_PAT_DON_PETRY org secret is empty or out of scope for ${GITHUB_REPOSITORY}."
_msg+=" It must be a CLASSIC PAT with repo scope whose owner has admin on this repo"
_msg+=" — fine-grained PATs and GITHUB_TOKEN are rejected by the check-suites/rulesets APIs."
_msg+=" See standards/github-settings.md."
echo "::error title=Missing GH_PAT_DON_PETRY::${_msg}"
{
echo "## Apply repo settings — preflight failed"
echo ""
echo "\`GH_PAT_DON_PETRY\` is empty or out of scope for \`${GITHUB_REPOSITORY}\`."
echo ""
echo "Set it as an **org secret** (classic PAT, \`repo\` scope, owner has admin) with visibility covering this repo. See \`standards/github-settings.md\` §Organization-Level Secrets."
} >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
echo "GH_PAT_DON_PETRY present — proceeding."

- name: Checkout petry-projects/.github (scripts at the pinned channel)
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: petry-projects/.github
ref: ${{ inputs.checkout_ref }}
persist-credentials: false

- name: Apply settings and rulesets to the calling repo
env:
GH_TOKEN: ${{ secrets.GH_PAT_DON_PETRY }}
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
repo="${GITHUB_REPOSITORY##*/}"
echo "Applying standard settings to ${repo} (dry_run=${DRY_RUN}) ..."
bash scripts/apply-repo-settings.sh "$repo"
echo "Applying branch-policy rulesets (pr-quality, code-quality) to ${repo} ..."
bash scripts/apply-rulesets.sh "$repo"
37 changes: 36 additions & 1 deletion .github/workflows/apply-repo-settings-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,24 @@ on:
pull_request:
paths:
- 'scripts/apply-repo-settings.sh'
- 'scripts/apply-rulesets.sh'
- 'test/scripts/apply-repo-settings/**'
- '.github/workflows/apply-repo-settings-tests.yml'
- '.github/workflows/apply-repo-settings-reusable.yml'
- '.github/workflows/apply-repo-settings.yml'
- 'standards/workflows/apply-repo-settings.yml'
- 'standards/canary-rings.json'
push:
branches: [main]
paths:
- 'scripts/apply-repo-settings.sh'
- 'scripts/apply-rulesets.sh'
- 'test/scripts/apply-repo-settings/**'
- '.github/workflows/apply-repo-settings-tests.yml'
- '.github/workflows/apply-repo-settings-reusable.yml'
- '.github/workflows/apply-repo-settings.yml'
- 'standards/workflows/apply-repo-settings.yml'
- 'standards/canary-rings.json'

permissions: {}

Expand All @@ -33,11 +43,36 @@ jobs:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Install bats, shellcheck, jq
- name: Install bats, shellcheck, jq, and yq
run: |
set -euo pipefail
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends bats shellcheck jq
# Install a pinned mikefarah/yq so the suite is reproducible and not
# dependent on whatever version ships in the runner image.
YQ_VERSION="v4.44.3"
# --proto '=https' --proto-redir '=https' enforces HTTPS on both the
# initial request and any redirects (GitHub releases redirect to
# objects.githubusercontent.com).
# --retry 5 --retry-delay 2 --retry-connrefused --retry-all-errors
# rides out the transient network/5xx blips from that redirect target
# that otherwise flake this suite; the budget stays bounded so a
# genuinely-down mirror still fails the job fast.
curl -sSfL --proto '=https' --proto-redir '=https' --retry 5 --retry-delay 2 --retry-connrefused --retry-all-errors \
"https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" \
-o /tmp/yq_linux_amd64
curl -sSfL --proto '=https' --proto-redir '=https' --retry 5 --retry-delay 2 --retry-connrefused --retry-all-errors \
"https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/checksums" \
-o /tmp/yq_checksums
curl -sSfL --proto '=https' --proto-redir '=https' --retry 5 --retry-delay 2 --retry-connrefused --retry-all-errors \
"https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/checksums_hashes_order" \
-o /tmp/yq_checksums_order
# The checksums file puts filename first; use checksums_hashes_order to
# find the SHA-256 column position dynamically.
SHA256_COL=$(( $(grep -n "^SHA-256$" /tmp/yq_checksums_order | cut -d: -f1) + 1 ))
EXPECTED_SHA="$(grep "^yq_linux_amd64 " /tmp/yq_checksums | awk -v col="${SHA256_COL}" '{print $col}')"
echo "${EXPECTED_SHA} /tmp/yq_linux_amd64" | sha256sum -c -
sudo install -m 755 /tmp/yq_linux_amd64 /usr/local/bin/yq

- name: shellcheck
run: shellcheck --severity=warning -x scripts/apply-repo-settings.sh
Expand Down
38 changes: 38 additions & 0 deletions .github/workflows/apply-repo-settings.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Apply repo settings — host dogfood caller (ring0).
# .github hosts the reusable, so it self-heals through a LOCAL ./ ref (always the
# current version under test), mirroring dependabot-automerge.yml's dogfood
# convention (#541). Fleet repos adopt standards/workflows/apply-repo-settings.yml,
# which pins the released channel tag instead.
name: Apply repo settings

on:
schedule:
# Weekly self-heal. Offset from other crons to spread org API load.
- cron: '17 6 * * 1'
workflow_dispatch:
inputs:
dry_run:
description: "Preview changes without applying"
type: boolean
required: false
default: false
push:
branches: [main]
paths:
- '.github/workflows/apply-repo-settings.yml'

permissions: {}

concurrency:
group: apply-repo-settings-${{ github.ref }}
cancel-in-progress: false

jobs:
apply:
permissions:
contents: read
uses: ./.github/workflows/apply-repo-settings-reusable.yml # local ref — always current (ring0 dogfood, #541)
with:
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false }}
checkout_ref: ${{ github.sha }} # dogfood the exact version under test
secrets: inherit # NOSONAR(githubactions:S7635) first-party trusted reusable
64 changes: 64 additions & 0 deletions standards/canary-rings.json
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,70 @@
}
}
},
"apply-repo-settings": {
"host": "petry-projects/.github",
"reusable": ".github/workflows/apply-repo-settings-reusable.yml",
"run_workflow": "Apply repo settings",
"rings": [
{
"channel": "next",
"order": 0,
"members": [
"petry-projects/.github-private"
]
},
{
"channel": "ring0",
"order": 1,
"members": [
"petry-projects/.github"
]
},
{
"channel": "ring1",
"order": 2,
"members": [
"petry-projects/markets",
"petry-projects/TalkTerm",
"petry-projects/bmad-bgreat-suite"
]
},
{
"channel": "stable",
"order": 3,
"members": [
"*"
]
}
],
"gate": {
"_standard": "petry-projects/.github#548 — graduated dwell/sample gate over a per-candidate cumulative window. These are registry-configurable per-transition knobs; the values below are the #548 defaults.",
"_cadence_note": "This workflow is weekly-cron (petry-projects/.github#984), so sample floors starve during soak. next->ring0 waives the sample when no caller run exists yet; ring members feed the gate via a manual workflow_dispatch after the stub lands. Do NOT paper over a starved gate with --override.",
"baseline_window_days": 14,
"baseline_spike_cap_multiple": 3,
"benign_failure_classes": [],
"control": {
"allow_pre_existing": false
},
"transitions": {
"next->ring0": {
"dwell_hours": 4,
"sample_fraction_permille": 250,
"sample_clamp_min": 3,
"sample_clamp_max": 15,
"waive_sample_if_no_caller": true
},
"ring0->ring1": {
"dwell_hours": 8,
"waive_sample": true
},
"ring1->stable": {
"dwell_hours": 12,
"sample_min": 1
}
}
}
},
"dependabot-rebase": {
"host": "petry-projects/.github",
"reusable": ".github/workflows/dependabot-rebase-reusable.yml",
Expand Down
1 change: 1 addition & 0 deletions standards/ci-standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ filename doesn't carry the `-reusable.yml` suffix (grandfathered exception:
| [`initiative-driver.yml`](workflows/initiative-driver.yml) | 1 | Dispatches the central initiative-driver to release ready sub-issues of `initiative:auto` epics to dev-lead — **required org-wide** (#844) |
| [`pr-review-mention.yml`](workflows/pr-review-mention.yml) | 1 | Trigger the pr-review agent when `@donpetry-bot` is mentioned or `donpetry-bot` is assigned as reviewer |
| [`persona-mention.yml`](workflows/persona-mention.yml) | 1 | Route `@petry-projects/<role>` mentions to the addressed persona — one router for **all** personas ([persona-standards.md §4.1](persona-standards.md)) |
| [`apply-repo-settings.yml`](workflows/apply-repo-settings.yml) | 1 | Weekly branch-policy compliance self-heal — applies standard repo settings + `pr-quality`/`code-quality` rulesets ([github-settings.md](github-settings.md), [ruleset-remediation-runbook.md](ruleset-remediation-runbook.md)). Requires org secret `GH_PAT_DON_PETRY`. |
| [`copilot-setup-steps.yml`](workflows/copilot-setup-steps.yml) | 2 | Pre-install tools and dependencies for Copilot cloud agent sessions |

**Adapt only when the template genuinely requires repo-specific content** (e.g., a
Expand Down
17 changes: 13 additions & 4 deletions standards/github-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,14 +435,22 @@ no PATCH is needed and no finding is raised until the app is first seen in the r
**Additional AI engines** (Gemini, Copilot) — if a repo activates an alternative `DEV_LEAD_ENGINE`:
Verify that the corresponding app has `auto_trigger_checks: false` set if/when it first creates a check run.

**Applying manually** (requires a classic PAT with `repo` scope — OAuth app tokens are rejected by this API endpoint):
**Applying** — the fleet self-heals weekly via the **`Apply repo settings`** workflow
(the org reusable `apply-repo-settings-reusable.yml`, adopted per repo through the thin
caller stub `standards/workflows/apply-repo-settings.yml`). To apply on demand, dispatch it:

```bash
GH_TOKEN=<classic-pat> bash scripts/apply-repo-settings.sh <repo-name>
# or for all org repos:
GH_TOKEN=<classic-pat> bash scripts/apply-repo-settings.sh --all
# Dispatch the self-heal for one repo (add -f dry_run=true to preview):
gh workflow run "Apply repo settings" --repo petry-projects/<repo-name>
```

The workflow authenticates with the org secret **`GH_PAT_DON_PETRY`** (classic PAT, `repo`
scope, owner has repo-admin). The check-suites/preferences and ruleset APIs are legacy and
reject fine-grained PATs and `GITHUB_TOKEN` (403), which is why a classic admin PAT is
required. Running the scripts by hand (`GH_TOKEN=<classic-admin-pat> bash
scripts/apply-repo-settings.sh <repo-name>`) remains possible for break-glass, but the
dispatched workflow is the standard path.

### Other Integrations

| Integration | Purpose | Scope |
Expand All @@ -464,6 +472,7 @@ all repos automatically — no per-repo setup needed:
| `APP_PRIVATE_KEY` | GitHub App private key for Dependabot auto-merge |
| `CLAUDE_CODE_OAUTH_TOKEN` | Authentication for Claude Code Action and dev-lead agent (default engine) |
| `DON_PETRY_BOT_GH_PAT` | Classic PAT (repo scope) owned by donpetry-bot; required by `pr-review-mention-reusable.yml` to post review-mention comments as the bot identity |
| `GH_PAT_DON_PETRY` | Classic PAT owned by donpetry-bot with **`repo` scope AND repo-admin on target repos**; used by the `Apply repo settings` self-heal (`apply-repo-settings-reusable.yml`) for the check-suites/preferences and ruleset admin APIs, which are legacy and **reject fine-grained PATs and `GITHUB_TOKEN` (403)**. Visibility must cover the whole fleet (including public repos) or the workflow preflight fails loud. Distinct from `DON_PETRY_BOT_GH_PAT` above (repo-scope only, no admin, review-mention identity) — do not conflate the two. |
| `GH_PAT_WORKFLOWS` | Classic PAT with `repo` scope; required for cross-repo script access and dev-lead to push workflow files |
| `GITLEAKS_LICENSE` | Gitleaks license key required for `secret-scan` job in organization repositories (see [ci-standards.md](ci-standards.md#4-secret-scanning-ciymll--gitleaks-job)) |
| `SONAR_TOKEN` | SonarCloud analysis authentication |
Expand Down
11 changes: 11 additions & 0 deletions standards/ruleset-remediation-runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ weekly compliance audit (`check_ruleset_bypass_actors()` and
> `administration:write`) that the CI `GITHUB_TOKEN` does not carry — so these
> findings are detected and filed every week but never auto-applied. Run this
> runbook with an admin token to close them.
>
> **Weekly self-heal (preferred first line).** The `Apply repo settings` workflow
> (org reusable [`apply-repo-settings-reusable.yml`](../.github/workflows/apply-repo-settings-reusable.yml),
> adopted per repo via [`standards/workflows/apply-repo-settings.yml`](workflows/apply-repo-settings.yml))
> now runs `apply-rulesets.sh` on a **weekly cron**, converging `pr-quality` /
> `code-quality` to the codified `standards/rulesets/*.json` — this is what fixes the
> recurring `require_last_push_approval` drift (petry-projects/.github#984). It
> authenticates with the classic admin PAT `GH_PAT_DON_PETRY`. To remediate now
> without waiting for the cron, dispatch it: `gh workflow run "Apply repo settings"
> --repo petry-projects/<repo>`. This runbook remains the path for **bypass-actor**
> and **legacy-ruleset** findings, which `apply-rulesets.sh` does not cover.

See [`github-settings.md` § Repository Rulesets](github-settings.md#repository-rulesets)
for the policy this enforces:
Expand Down
55 changes: 55 additions & 0 deletions standards/workflows/apply-repo-settings.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# ─────────────────────────────────────────────────────────────────────────────
# SOURCE OF TRUTH: petry-projects/.github/standards/workflows/apply-repo-settings.yml
# Standard: petry-projects/.github/standards/github-settings.md
# petry-projects/.github/standards/ruleset-remediation-runbook.md
# Reusable: petry-projects/.github/.github/workflows/apply-repo-settings-reusable.yml
#
# AGENTS — READ BEFORE EDITING:
# • This file is a THIN CALLER STUB. All settings/ruleset logic lives in the
# reusable workflow above; consumer repos carry NO script copy.
# • You MAY change: the cron offset only (stagger org API load).
# • You MUST NOT change: the `uses:` line, `secrets: inherit`, the job-level
# `permissions:` block, or the `checkout_ref` forward — reusable workflows can
# be granted no more permissions than the calling job has, and forwarding an
# input the pinned channel does not declare breaks every run at startup (#1034).
# • If you need different behaviour, open a PR against the reusable in the
# central repo.
# ─────────────────────────────────────────────────────────────────────────────
#
# Apply repo settings — thin caller for the org-level compliance self-heal.
# To adopt: copy this file verbatim to .github/workflows/apply-repo-settings.yml.
# Required org secret (inherited):
# GH_PAT_DON_PETRY — classic PAT, repo scope, owner has admin on this repo.
name: Apply repo settings

on:
schedule:
# Weekly self-heal. The cron offset MAY be changed to stagger org API load.
- cron: '17 6 * * 1'
workflow_dispatch:
inputs:
dry_run:
description: "Preview changes without applying"
type: boolean
required: false
default: false
push:
branches: [main]
paths:
- '.github/workflows/apply-repo-settings.yml'

permissions: {}

concurrency:
group: apply-repo-settings-${{ github.ref }}
cancel-in-progress: false

jobs:
apply:
permissions:
contents: read
uses: petry-projects/.github/.github/workflows/apply-repo-settings-reusable.yml@apply-repo-settings/v1-stable # NOSONAR(githubactions:S7637) first-party channel ref
with:
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run == true }}
checkout_ref: apply-repo-settings/v1-stable # keep in lockstep with the uses: channel pin
secrets: inherit # NOSONAR(githubactions:S7635) first-party trusted reusable
Loading
Loading