Skip to content

Ship branch rulesets as versioned JSON and add them to the re-sync / drift-check loop #211

Description

@ptr727

Problem

Branch rulesets are live GitHub config applied once at repo creation (README "Rules / Rulesets", Steps 1–2: export the template's live rulesets and POST them to the new repo). They are not files, so they sit outside the "Staying in Sync and Reporting Drift Upstream" re-sync loop (AGENTS.md), which only re-applies file (verbatim-carry) artifacts.

Two consequences fall out of that:

  1. A ruleset change in the template never propagates to already-derived repos.
  2. Ruleset drift in a derived repo is invisible — nothing diffs live config against an expected state — until it breaks something.

This just bit a derived repo (ptr727/PlexCleaner)

#82 (closed) corrected the template's develop ruleset (strict_required_status_checks_policyfalse) and updated the branching docs. PlexCleaner received the corrected docs through the normal file re-sync — its AGENTS.md correctly states the strict flag is off on both branches — but its actual rulesets stayed strict=true on BOTH develop and main, because rulesets aren't in the re-sync loop.

Net effect: docs and live config silently contradicted each other. The develop ruleset reproduced the exact #82 failure mode (a BEHIND bot PR can't auto-merge; it sat OPEN with green checks), and main's strict flag would have forced every develop → main release through an admin bypass. Both were fixed locally today by PUTting each ruleset back to strict=false — but only after it broke auto-merge and blocked a promotion.

So #82 fixed the value; this issue is about the propagation/drift mechanism that let a corrected template ruleset never reach an existing downstream, and let a downstream's rulesets drift undetected.

Proposal

1. Rulesets as versioned JSON (source of truth in-repo)

Commit the canonical definitions as files:

  • .github/rulesets/develop.json
  • .github/rulesets/main.json

Each holds the re-importable writable subset — {name, target, enforcement, bypass_actors, conditions, rules} — i.e. exactly what README Step 1 already extracts from the live export. These become the single, reviewable, PR-gated source of truth instead of "whatever the live template ruleset happens to be at export time." Setup imports from the committed files:

for b in develop main; do
  gh api -X POST "repos/<owner>/<repo>/rulesets" --input ".github/rulesets/$b.json"
done

The JSON ports verbatim with no placeholders: conditions keys on ref_name (refs/heads/develop | refs/heads/main), not the repo; bypass_actors uses the Admin repository role (actor_id: 5), a global id; the required check binds by name (Check pull request workflow status). So a committed file is repo-agnostic as-is.

2. Add rulesets to the re-sync / drift loop

  • List .github/rulesets/*.json among the verbatim-carry artifacts in "Staying in Sync."
  • Add a documented drift check: fetch the derived repo's live rulesets, normalize to the same writable subset, and diff against the committed JSON; correct via a full-payload PUT (GET → change → PUT the whole object — partial PUTs 422, already documented in "Rules / Rulesets"). Sketch:
for b in develop main; do
  id=$(gh api "repos/<owner>/<repo>/rulesets" --jq ".[]|select(.name==\"$b\").id")
  diff <(jq -S '{name,target,enforcement,bypass_actors,conditions,rules}' ".github/rulesets/$b.json") \
       <(gh api "repos/<owner>/<repo>/rulesets/$id" --jq '{name,target,enforcement,bypass_actors,conditions,rules}' | jq -S '.') \
    && echo "$b: in sync" || echo "$b: DRIFT (see diff)"
done
  • Optionally ship it as scripts/verify-rulesets.sh and/or a scheduled CI workflow in derived repos, so drift is caught automatically rather than after it breaks auto-merge.

Why now / acceptance

  • A template ruleset change (like Develop ruleset strict "branches up to date" flag stalls bot auto-merge when sibling PR lands mid-CI #82) reaches existing derived repos via the normal re-sync, instead of silently not propagating.
  • Drift (strict re-enabled, a rule dropped, a merge-method changed) is detectable by diffing live vs committed JSON.
  • Acceptance: .github/rulesets/{develop,main}.json committed and used by setup's import; "Staying in Sync" lists them with the verify command; a derived repo can run one command to confirm its live rulesets match the template's intent.

Notes

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions