From 75d3839f49aeda51c4d7c24b884455bc292739a1 Mon Sep 17 00:00:00 2001
From: Don Petry <donpetry@gmail.com>
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) <noreply@anthropic.com>
---
 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/<repo> 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) <noreply@anthropic.com>
---
 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/<repo> DRY_RUN=true bash scripts/apply-rulesets.sh
+GH_TOKEN=<admin-token> bash scripts/apply-rulesets.sh <repo> --dry-run
 ```
 
 ## Scope boundary
