From 75d3839f49aeda51c4d7c24b884455bc292739a1 Mon Sep 17 00:00:00 2001 From: Don Petry Date: Thu, 2 Jul 2026 09:22:58 -0500 Subject: [PATCH 1/3] chore(rulesets): relocate code-quality + pr-quality JSON to standards/rulesets/ (#575) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the org-wide compliance ruleset source of truth into its canonical home in petry-projects/.github. `.github` owns org-wide standards and compliance policy; `.github-private` is scoped to agents/skills and their assets (repo boundary codified in #576). - Add standards/rulesets/{code-quality,pr-quality}.json — byte-identical to the current .github-private/.github/rulesets/ copies (the 4-check code-quality set; NO coverage/secret-scan additions, which are sequenced separately to avoid bricking fleet repos that don't yet produce those checks). - Add standards/rulesets/README.md documenting source-of-truth, the scope boundary (release-channel-tags stays in .github-private), and the safe-sequencing rule for required-check additions. - github-settings.md: add a "Source of truth" pointer to standards/rulesets/. - AGENTS.md: add a Rulesets row to the Organization Standards index. The tooling repoint (apply-rulesets.sh + bootstrap-new-repo.sh) and removal of the JSONs from .github-private land in a follow-up PR against .github-private, which must merge after this one. Part of #575. Follows #576. Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 1 + standards/github-settings.md | 9 ++++++ standards/rulesets/README.md | 43 ++++++++++++++++++++++++++++ standards/rulesets/code-quality.json | 37 ++++++++++++++++++++++++ standards/rulesets/pr-quality.json | 36 +++++++++++++++++++++++ 5 files changed, 126 insertions(+) create mode 100644 standards/rulesets/README.md create mode 100644 standards/rulesets/code-quality.json create mode 100644 standards/rulesets/pr-quality.json diff --git a/AGENTS.md b/AGENTS.md index 93528898..19cc279b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,6 +19,7 @@ Read the relevant standard *before* making changes that touch CI, repo settings, | **Workflow templates** | [`standards/workflows/`](https://github.com/petry-projects/.github/tree/main/standards/workflows) | Copy-paste-ready templates: `agent-shield.yml`, `dev-lead.yml`, `dependabot-automerge.yml`, `dependabot-rebase.yml`, `dependency-audit.yml`, `feature-ideation.yml` | | **Agent configuration** | [`standards/agent-standards.md`](https://github.com/petry-projects/.github/blob/main/standards/agent-standards.md) | CLAUDE.md / AGENTS.md / SKILL.md required structure, frontmatter rules, cross-references | | **Repo settings + labels** | [`standards/github-settings.md`](https://github.com/petry-projects/.github/blob/main/standards/github-settings.md) | Required settings, label set with exact colors, code-quality ruleset, branch protection | +| **Rulesets** | [`standards/rulesets/`](https://github.com/petry-projects/.github/tree/main/standards/rulesets) | Codified source-of-truth JSON for the org-wide `pr-quality` + `code-quality` rulesets (applied by `apply-rulesets.sh`). `release-channel-tags` stays repo-local in `.github-private` | | **Dependabot config** | [`standards/dependabot-policy.md`](https://github.com/petry-projects/.github/blob/main/standards/dependabot-policy.md) and [`standards/dependabot/`](https://github.com/petry-projects/.github/tree/main/standards/dependabot) | Per-ecosystem dependabot.yml templates and policy | | **Advanced Security (GHAS)** | [`standards/advanced-security.md`](https://github.com/petry-projects/.github/blob/main/standards/advanced-security.md) | Org-wide GHAS enablement via the "GitHub recommended" code security configuration, licensing model, and how to verify push protection actually enforces | | **Push protection** | [`standards/push-protection.md`](https://github.com/petry-projects/.github/blob/main/standards/push-protection.md) | Secret scanning + push protection, local gitleaks hooks, CI secret-scan job, incident response | diff --git a/standards/github-settings.md b/standards/github-settings.md index 6742cfe7..5ca45aba 100644 --- a/standards/github-settings.md +++ b/standards/github-settings.md @@ -152,6 +152,15 @@ silently drops a merge gate. `check_legacy_rulesets()` in legacy ruleset and reports the exact migration delta (checks to move into `code-quality` first, or "safe to delete"). +**Source of truth.** The codified `pr-quality` and `code-quality` ruleset JSONs +live in this repo at [`standards/rulesets/`](rulesets/) — `.github` owns org-wide +compliance policy (see [`AGENTS.md`](../AGENTS.md#organization-standards) for the +repo-boundary rule, codified in #576). `apply-rulesets.sh` reads these files and +converges each repo's live ruleset to them. The one ruleset that stays in +`petry-projects/.github-private` is `release-channel-tags` — it protects that +repo's own `pr-review/**` and `dev-lead/**` agent-release tags and is therefore +correctly repo-local. + > **Remediating ruleset findings is a manual, admin-token procedure** — > `compliance-remediate.sh` skips the `rulesets` category. Follow the > [Ruleset Remediation Runbook](ruleset-remediation-runbook.md) (snapshot → diff --git a/standards/rulesets/README.md b/standards/rulesets/README.md new file mode 100644 index 00000000..7eae12f6 --- /dev/null +++ b/standards/rulesets/README.md @@ -0,0 +1,43 @@ +# Repository Rulesets — codified source of truth + +This directory holds the **org-wide compliance ruleset JSONs** for `petry-projects`. +`.github` owns org-wide standards and compliance policy; its canonical home is +`standards/`. See [`AGENTS.md`](../../AGENTS.md#organization-standards) for the +repo-boundary rule (codified in petry-projects/.github#576) and +[`standards/github-settings.md`](../github-settings.md#repository-rulesets) for how +these rulesets are enforced. + +| File | Ruleset | Target | Enforces | +|------|---------|--------|----------| +| [`pr-quality.json`](pr-quality.json) | `pr-quality` | `~DEFAULT_BRANCH` | 1 approval, code-owner review, thread resolution, dismiss-stale, squash-only merge | +| [`code-quality.json`](code-quality.json) | `code-quality` | `~DEFAULT_BRANCH` | Required status checks (SonarCloud, CodeQL, agent-shield, dependency-audit) | + +Both carry the two mandatory bypass actors — `OrganizationAdmin` and the +`dependabot-automerge-petry` Integration app (id `3167543`), both `bypass_mode: always`. + +## Applying + +`scripts/apply-rulesets.sh` reads these files and creates/updates the named ruleset +on a target repo — re-running converges to the file's desired state (a no-op when +already in sync). Rulesets live **on each repo**; editing a file here changes the +desired state, not any live ruleset, until the applier runs. + +```bash +# Preview the payload for one repo (no writes): +RULESETS_REPO=petry-projects/ DRY_RUN=true bash scripts/apply-rulesets.sh +``` + +## Scope boundary + +- **Fleet-wide → here.** `pr-quality` / `code-quality` are org-wide policy. +- **Protects an agent/skill's own assets → stays in `.github-private`.** + `release-channel-tags` lives in `petry-projects/.github-private` because it + protects that repo's own `pr-review/**` and `dev-lead/**` release tags. + +## Changing the required-check set + +Adding a required status-check context to `code-quality.json` makes it a **merge +gate on every repo the ruleset targets**. A repo that does not *produce* that check +is bricked (the required check never reports). Sequence any additions safely: ship +the producing CI job fleet-wide first, then add the context here. Keep stricter sets +scoped to the template / new repos until the fleet backfill lands. diff --git a/standards/rulesets/code-quality.json b/standards/rulesets/code-quality.json new file mode 100644 index 00000000..d64cb211 --- /dev/null +++ b/standards/rulesets/code-quality.json @@ -0,0 +1,37 @@ +{ + "name": "code-quality", + "target": "branch", + "enforcement": "active", + "bypass_actors": [ + { + "actor_type": "OrganizationAdmin", + "bypass_mode": "always" + }, + { + "actor_id": 3167543, + "actor_type": "Integration", + "bypass_mode": "always" + } + ], + "conditions": { + "ref_name": { + "include": ["~DEFAULT_BRANCH"], + "exclude": [] + } + }, + "rules": [ + { + "type": "required_status_checks", + "parameters": { + "required_status_checks": [ + {"context": "SonarCloud"}, + {"context": "CodeQL"}, + {"context": "agent-shield / AgentShield"}, + {"context": "dependency-audit / Detect ecosystems"} + ], + "strict_required_status_checks_policy": true, + "do_not_enforce_on_create": false + } + } + ] +} diff --git a/standards/rulesets/pr-quality.json b/standards/rulesets/pr-quality.json new file mode 100644 index 00000000..585ed85b --- /dev/null +++ b/standards/rulesets/pr-quality.json @@ -0,0 +1,36 @@ +{ + "name": "pr-quality", + "target": "branch", + "enforcement": "active", + "bypass_actors": [ + { + "actor_type": "OrganizationAdmin", + "bypass_mode": "always" + }, + { + "actor_id": 3167543, + "actor_type": "Integration", + "bypass_mode": "always" + } + ], + "conditions": { + "ref_name": { + "include": ["~DEFAULT_BRANCH"], + "exclude": [] + } + }, + "rules": [ + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 1, + "require_code_owner_review": true, + "required_review_thread_resolution": true, + "dismiss_stale_reviews_on_push": true, + "require_last_push_approval": false, + "automatic_copilot_code_review_enabled": false, + "allowed_merge_methods": ["squash"] + } + } + ] +} From 0dc0f023f805f7bcda720479576614cb483c4902 Mon Sep 17 00:00:00 2001 From: Don Petry <36422719+don-petry@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:35:43 -0500 Subject: [PATCH 2/3] =?UTF-8?q?fix(pr-quality):=20require=20last=20push=20?= =?UTF-8?q?approval=20=E2=80=94=20align=20codified=20ruleset=20with=20org?= =?UTF-8?q?=20policy=20(#575)=20(#582)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The codified pr-quality had require_last_push_approval=false, baked in when the file was authored (#972). That contradicts the org's actual policy: the meta-repos (.github, .github-private) and newer repos (TalkTerm, google-app-scripts) already enforce require_last_push_approval=true live, and #895 verified auto-rebase's update-branch is EXEMPT from last-push-approval (so enabling it does not fight the auto-rebase flow). Set the codified source of truth to true. Blast radius on next apply-rulesets run: tightens the three drifted repos still at false (ContentTwin, markets, broodly); no-op on the four already at true. Stacked on #577 (the relocation). Resolves the pr-quality file-vs-live drift flagged in petry-projects/.github-private#1013. Co-authored-by: Claude Opus 4.8 (1M context) --- standards/rulesets/pr-quality.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standards/rulesets/pr-quality.json b/standards/rulesets/pr-quality.json index 585ed85b..93ce6652 100644 --- a/standards/rulesets/pr-quality.json +++ b/standards/rulesets/pr-quality.json @@ -27,7 +27,7 @@ "require_code_owner_review": true, "required_review_thread_resolution": true, "dismiss_stale_reviews_on_push": true, - "require_last_push_approval": false, + "require_last_push_approval": true, "automatic_copilot_code_review_enabled": false, "allowed_merge_methods": ["squash"] } From e75495649ff640fb2aec27ace087df702773957b Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Thu, 2 Jul 2026 22:39:20 +0000 Subject: [PATCH 3/3] chore: dev-lead update (review-changes) [skip ci-relay] --- standards/github-settings.md | 4 ++-- standards/rulesets/README.md | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/standards/github-settings.md b/standards/github-settings.md index 5ca45aba..57962459 100644 --- a/standards/github-settings.md +++ b/standards/github-settings.md @@ -155,8 +155,8 @@ legacy ruleset and reports the exact migration delta (checks to move into **Source of truth.** The codified `pr-quality` and `code-quality` ruleset JSONs live in this repo at [`standards/rulesets/`](rulesets/) — `.github` owns org-wide compliance policy (see [`AGENTS.md`](../AGENTS.md#organization-standards) for the -repo-boundary rule, codified in #576). `apply-rulesets.sh` reads these files and -converges each repo's live ruleset to them. The one ruleset that stays in +repo-boundary rule, codified in #576). Run `apply-rulesets.sh` to converge each +repo's live ruleset to the desired state documented here. The one ruleset that stays in `petry-projects/.github-private` is `release-channel-tags` — it protects that repo's own `pr-review/**` and `dev-lead/**` agent-release tags and is therefore correctly repo-local. diff --git a/standards/rulesets/README.md b/standards/rulesets/README.md index 7eae12f6..770b743d 100644 --- a/standards/rulesets/README.md +++ b/standards/rulesets/README.md @@ -17,14 +17,14 @@ Both carry the two mandatory bypass actors — `OrganizationAdmin` and the ## Applying -`scripts/apply-rulesets.sh` reads these files and creates/updates the named ruleset -on a target repo — re-running converges to the file's desired state (a no-op when -already in sync). Rulesets live **on each repo**; editing a file here changes the +These files are the **desired-state source of truth**. `scripts/apply-rulesets.sh` +creates/updates the named ruleset on a target repo to match — re-running is a no-op +when already in sync. Rulesets live **on each repo**; editing a file here changes the desired state, not any live ruleset, until the applier runs. ```bash # Preview the payload for one repo (no writes): -RULESETS_REPO=petry-projects/ DRY_RUN=true bash scripts/apply-rulesets.sh +GH_TOKEN= bash scripts/apply-rulesets.sh --dry-run ``` ## Scope boundary