Skip to content
Merged
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
61 changes: 50 additions & 11 deletions scripts/compliance-audit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -422,28 +422,67 @@ check_codeowners() {
if [ "$found" = false ]; then
add_finding "$repo" "settings" "missing-codeowners" "error" \
"No \`CODEOWNERS\` file found — required for code owner review enforcement (pr-quality ruleset)" \
"standards/github-settings.md#codeowners-standard"
"standards/codeowners-standard.md"
return
fi

# Extract non-comment, non-blank owner lines for accurate matching.
# Each such line has the form: <pattern> <owner1> [<owner2> ...]
# We check that every owner line includes both required bot accounts.
# Standard (codeowners-standard.md):
# 1. @petry-projects/org-leads MUST be the FIRST owner on every line.
# 2. Additional teams (@petry-projects/<slug>) are allowed.
# 3. Individual users (@username without "/") are forbidden.
local owner_lines
owner_lines=$(echo "$codeowners_content" | grep -v '^\s*#' | grep -v '^\s*$')

local missing_bots=()
if ! echo "$owner_lines" | grep -qE '@petry-projects-pr-review-agent(\s|$)'; then
missing_bots+=("@petry-projects-pr-review-agent")
if [ -z "$owner_lines" ]; then
add_finding "$repo" "settings" "codeowners-empty" "error" \
"CODEOWNERS file has no owner lines (only comments/blank)" \
"standards/codeowners-standard.md"
return
fi

# Rule 1: @petry-projects/org-leads MUST be the first owner on every line
# (the first whitespace-separated token after the pattern).
local bad_first_owner=""
while IFS= read -r line; do
[ -z "$line" ] && continue
# awk $2 = first owner token after the pattern
local first_owner
first_owner=$(echo "$line" | awk '{print $2}')
if [ "$first_owner" != "@petry-projects/org-leads" ]; then
bad_first_owner="$line"
break
fi
done <<< "$owner_lines"
if [ -n "$bad_first_owner" ]; then
add_finding "$repo" "settings" "codeowners-org-leads-not-first" "error" \
"CODEOWNERS owner lines must list \`@petry-projects/org-leads\` as the FIRST owner. Offending line: \`$bad_first_owner\`" \
"standards/codeowners-standard.md"
fi
if ! echo "$owner_lines" | grep -qE '@dependabot-automerge-petry(\s|$)'; then
missing_bots+=("@dependabot-automerge-petry")

# Rule 3: no individual users — every owner token must be a team (contain "/").
# Collect owner tokens from all lines (everything starting with @).
local individual_owners
individual_owners=$(echo "$owner_lines" \
| tr ' \t' '\n' \
| grep -E '^@' \
| grep -vE '^@[^/]+/' \
| sort -u || true)
if [ -n "$individual_owners" ]; then
local joined
joined=$(echo "$individual_owners" | tr '\n' ' ')
add_finding "$repo" "settings" "codeowners-individual-users" "error" \
"CODEOWNERS contains forbidden individual user owners: ${joined% } — only teams (@petry-projects/<slug>) are allowed; manage membership via teams" \
"standards/codeowners-standard.md"
fi

if [ "${#missing_bots[@]}" -gt 0 ]; then
add_finding "$repo" "settings" "codeowners-missing-bots" "error" \
"CODEOWNERS is missing required bot accounts on owner lines: ${missing_bots[*]} — bot approvals will not satisfy require_code_owner_review" \
"standards/github-settings.md#codeowners-standard"
# Advisory: a catch-all `*` pattern should exist so files unmatched by any
# path-specific rule still have an owner.
if ! echo "$owner_lines" | awk '{print $1}' | grep -qxF '*'; then
add_finding "$repo" "settings" "codeowners-no-catchall" "warning" \
"CODEOWNERS has no default \`*\` catch-all pattern — files not matched by a path rule will have no owner and \`require_code_owner_review\` will not apply to them" \
"standards/codeowners-standard.md"
fi
}

Expand Down
97 changes: 81 additions & 16 deletions standards/codeowners-standard.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,107 @@
# CODEOWNERS Standard

**Status:** Active
**Status:** Required
**Applies to:** All repos in `petry-projects` org
**Enforced by:** [`compliance-audit.sh`](../scripts/compliance-audit.sh) (`check_codeowners`)
**Migration completed:** 2026-05-04

## Rule

CODEOWNERS files MUST reference the `@petry-projects/org-leads` team rather than individual users or bot accounts.
Every owner line in a CODEOWNERS file MUST follow these rules:

1. **`@petry-projects/org-leads` MUST be the FIRST owner** on every owner line
(so it always satisfies `require_code_owner_review`, regardless of
path-specific overrides).
2. **Additional teams are allowed** as subsequent owners when finer-grained
ownership is desired (e.g., `@petry-projects/security-leads` for
`/SECURITY.md`).
3. **Individual named users are forbidden** as owners. All owners MUST be
teams in the form `@petry-projects/<team-slug>`. Manage individual
membership through team membership instead.

```text
# Default
# Default — only org-leads
* @petry-projects/org-leads

# Path-specific — org-leads first, then additional team(s)
/security/ @petry-projects/org-leads @petry-projects/security-leads
/.github/ @petry-projects/org-leads @petry-projects/platform-leads
```

Path-specific rules MAY use additional teams or individuals when a narrower owner is appropriate, but the default `*` rule must always include `@petry-projects/org-leads`.
The legacy direct-listing pattern is **forbidden**:

```text
# DO NOT USE — individuals and bot accounts as owners
* @don-petry @petry-projects-pr-review-agent @dependabot-automerge-petry
```

## Why

- **Stable CODEOWNERS files** — adding/removing an owner is a team membership change, not a multi-repo PR
- **CODEOWNERS support for automation** — GitHub Apps cannot be listed in CODEOWNERS
(platform limitation), but a machine user in the team can satisfy
`require_code_owner_review`. See [pr-review-agent issue #27](https://github.com/don-petry/pr-review-agent/issues/27)
- **Centralized control** — team membership is managed in one place via the org admin UI
- **Stable CODEOWNERS files** — adding/removing an owner is a team membership
change, not a multi-repo PR
- **CODEOWNERS support for automation** — GitHub Apps **cannot** be listed in
CODEOWNERS (platform limitation,
[community discussion #23064](https://github.com/orgs/community/discussions/23064)).
A machine user in the team **can** satisfy `require_code_owner_review`. See
[pr-review-agent issue #27](https://github.com/don-petry/pr-review-agent/issues/27)
- **Centralized control** — team membership is managed in one place via the
org admin UI; no PR churn when membership changes

## Team Composition

The `@petry-projects/org-leads` team contains:
The [`@petry-projects/org-leads`](https://github.com/orgs/petry-projects/teams/org-leads)
team contains:

- `@don-petry` — primary maintainer (team maintainer)
- `@donpetry-bot` — automation machine user (used by pr-review-agent and similar tooling)
- `@donpetry-bot` — automation machine user (used by pr-review-agent and
similar tooling)

Add additional human maintainers as needed. Bots/apps that need code-owner
standing MUST be machine-user accounts added to this team, not GitHub Apps.

Add additional human maintainers as needed. Bots/apps that need code owner standing should be machine user accounts added to this team, not GitHub Apps.
## Required Setup for New Bots

When adding a new bot/automation account to the team:

1. Create a GitHub user account (machine user) — not a GitHub App
2. Add the account to the `@petry-projects/org-leads` team
3. Generate a **fine-grained PAT** with **Resource owner = `petry-projects`**
- Resource owner MUST be the org. A PAT scoped to the bot's personal
namespace will show "no access to any repositories" even if the bot is
in the org.
- Required permissions: Contents (read), Pull requests (read+write),
Commit statuses (read), Checks (read), Metadata (read), Members (read)
4. If the org has a fine-grained PAT approval policy, an org admin must
approve the request at
[Settings → Personal access tokens](https://github.com/organizations/petry-projects/settings/personal-access-token-requests)
5. Store the PAT as a repo or org secret for use by GitHub Actions

## Branch Protection

Repos that require code owner review must set `require_code_owner_review: true` on protected branches. Approvals from any member of `@petry-projects/org-leads` will satisfy the requirement.
Repos that require code owner review must set `require_code_owner_review: true`
on protected branches. Approvals from any member of `@petry-projects/org-leads`
will satisfy the requirement.

> **CODEOWNERS approval timing:** GitHub does not retroactively re-evaluate
> existing reviews if CODEOWNERS changes. After a CODEOWNERS edit, the next
> approval (or a re-request) is what counts.

## Verified End-to-End

The standard was validated on 2026-05-04 against
[TalkTerm#159](https://github.com/petry-projects/TalkTerm/pull/159) — a PR
in a repo with `require_code_owner_review: true`. After:

1. Migrating CODEOWNERS to `* @petry-projects/org-leads`
2. Issuing the bot a `petry-projects`-scoped fine-grained PAT
3. Adding `@donpetry-bot` to the team

## Migration
`pr-review-agent` posted an approval that flipped `reviewDecision` from
`REVIEW_REQUIRED` to `APPROVED`, confirming code-owner satisfaction via
team membership.

Repos still using individual owner lists should migrate via PR replacing the legacy `* @don-petry @petry-projects-pr-review-agent @dependabot-automerge-petry` line with `* @petry-projects/org-leads`.
## Migration History

The `petry-projects-pr-review-agent` GitHub App reference is non-functional (GitHub Apps cannot be CODEOWNERS) and should be removed during migration.
| Date | Change |
|------|--------|
| 2026-05-04 | Initial team-based standard adopted; all 6 child repos migrated (ContentTwin#128, TalkTerm#160, broodly#172, google-app-scripts#252, markets#153, bmad-bgreat-suite#133) |
20 changes: 9 additions & 11 deletions standards/dependabot-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ security posture than chasing every minor/patch release.
2. **Version updates weekly** for GitHub Actions, since pinned action versions do not
affect application stability and staying current reduces CI attack surface.
3. **Labels** `security` and `dependencies` on every Dependabot PR for filtering and audit.
4. **Auto-merge** after all CI checks pass, using a GitHub App token to approve
and merge eligible PRs. Both bot accounts (`dependabot-automerge-petry` and
`petry-projects-pr-review-agent`) are listed as code owners in every repo's
`CODEOWNERS` file so their approvals satisfy `require_code_owner_review`
without any bypass. Eligible updates:
4. **Auto-merge** after all CI checks pass, approving and merging eligible PRs.
Approvals come from members of the `@petry-projects/org-leads` team listed
in every repo's `CODEOWNERS` file (see
[codeowners-standard.md](codeowners-standard.md)), so they satisfy
`require_code_owner_review` without any bypass. Eligible updates:
- **GitHub Actions**: all version bumps including major (SHA-pinned, no runtime impact)
- **App ecosystems**: patch and minor security updates only (major requires human review)
- **Indirect (transitive) dependencies**: all updates regardless of version bump
Expand Down Expand Up @@ -292,12 +292,10 @@ The workflow fails if any known vulnerability is found, blocking the PR from mer
— do **not** modify the secrets block or permissions.

> **Note:** The rebase workflow is **not** required for `require_code_owner_review`.
> The correct solution for CODEOWNERS enforcement is to list the bot accounts
> (`@dependabot-automerge-petry`, `@petry-projects-pr-review-agent`) as owners
> in every CODEOWNERS pattern — see the
> [CODEOWNERS Standard](github-settings.md#codeowners-standard). The earlier
> approach of using `gh api .../merge` as a bypass was fragile and has been
> superseded.
> The correct solution for CODEOWNERS enforcement is to list the
> `@petry-projects/org-leads` team in every CODEOWNERS pattern — see the
> [CODEOWNERS Standard](codeowners-standard.md). The earlier approach of
> using `gh api .../merge` as a bypass was fragile and has been superseded.
4. Add `workflows/dependency-audit.yml` to `.github/workflows/`.
5. **GitHub App secrets** — `APP_ID` and `APP_PRIVATE_KEY` are managed at the
**organization level** (`gh secret set <name> --org petry-projects --visibility all`),
Expand Down
40 changes: 15 additions & 25 deletions standards/github-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,9 @@ See [CI Standards](ci-standards.md) for workflow templates and patterns.
| App | Purpose | Installed |
|-----|---------|-----------|
| **Claude** | AI code review and PR assistance via Claude Code Action | 2026-03-20 |
| **dependabot-automerge-petry** | Provides approving review for Dependabot auto-merge; listed in CODEOWNERS so its approvals satisfy `require_code_owner_review` | 2026-03-23 |
| **petry-projects-pr-review-agent** | General PR review agent; listed in CODEOWNERS as the org-standard automation reviewer | 2026-04-01 |
| **dependabot-automerge-petry** | Provides approving review for Dependabot auto-merge | 2026-03-23 |
| **petry-projects-pr-review-agent** | (deprecated) GitHub App formerly used for PR review — replaced by `donpetry-bot` machine user in `@petry-projects/org-leads` because Apps cannot be CODEOWNERS | 2026-04-01 |
| **donpetry-bot** | Machine-user account in `@petry-projects/org-leads`; satisfies CODEOWNERS for automated PR review | 2026-05-04 |
| **SonarQube Cloud (SonarCloud)** | Code quality, security hotspots, coverage tracking | 2026-03-25 |
| **CodeRabbit AI** | AI-powered code review on PRs | 2026-03-25 |

Expand Down Expand Up @@ -325,38 +326,27 @@ All repositories MUST have these labels configured:
All repositories MUST have a `CODEOWNERS` file at `.github/CODEOWNERS`
(or `CODEOWNERS` at the repo root for repos with no `.github/` directory).

### Required Bot Accounts
The full policy lives in [`codeowners-standard.md`](codeowners-standard.md).
Summary:

Every CODEOWNERS file MUST include these two bot accounts alongside `@don-petry`
so that automated PR approvals satisfy the `require_code_owner_review` setting
in the `pr-quality` ruleset:

| Account | App | Role |
|---------|-----|------|
| `@petry-projects-pr-review-agent` | `petry-projects-pr-review-agent` | General org PR review bot |
| `@dependabot-automerge-petry` | `dependabot-automerge-petry` | Dependabot auto-merge approver |

The `pr-quality` ruleset requires **1 code owner approval**. With all three
accounts on every pattern, an approval from `@don-petry`, `@petry-projects-pr-review-agent`,
or `@dependabot-automerge-petry` satisfies the requirement — provided the approver
is not also the author of the last push to that branch (`require_last_push_approval`
prevents self-approval after one's own push). For Dependabot PRs this is never an
issue: Dependabot pushes the branch and a separate bot approves it.
- The default owner line MUST be `* @petry-projects/org-leads`
- Direct listings of users or bot accounts (e.g.,
`@petry-projects-pr-review-agent`, `@dependabot-automerge-petry`) are
**forbidden** — manage membership through the team instead
- GitHub Apps cannot be code owners (platform limitation); use machine-user
accounts added to the team

### Standard Template

```gitignore
# CODEOWNERS
# Each line is a pattern followed by one or more owners.
# Owners are matched in order, last matching pattern wins.
# Standard: https://github.com/petry-projects/.github/blob/main/standards/github-settings.md#codeowners-standard
# Standard: https://github.com/petry-projects/.github/blob/main/standards/codeowners-standard.md

# Default owner for all files
* @don-petry @petry-projects-pr-review-agent @dependabot-automerge-petry
* @petry-projects/org-leads
```

Repos with finer-grained path ownership (e.g., `/apps/api/`, `/infra/`) MUST
add the two bot accounts to every path-specific line, not just the default `*`.
Repos with finer-grained path ownership MUST include `@petry-projects/org-leads`
on every owner line so the team can always satisfy `require_code_owner_review`.

---

Expand Down
Loading