You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
A ruleset change in the template never propagates to already-derived repos.
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_policy → false) 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:
forbin 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:
forbin 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.
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
This largely formalizes the existing export/import (README "Rules / Rulesets") into committed files plus a periodic diff — minimal new surface, mostly relocating the source of truth from "the live template ruleset at export time" to "a versioned file."
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
POSTthem 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:
This just bit a derived repo (
ptr727/PlexCleaner)#82 (closed) corrected the template's
developruleset (strict_required_status_checks_policy→false) and updated the branching docs. PlexCleaner received the corrected docs through the normal file re-sync — itsAGENTS.mdcorrectly states the strict flag is off on both branches — but its actual rulesets stayedstrict=trueon BOTHdevelopandmain, 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
BEHINDbot PR can't auto-merge; it sat OPEN with green checks), andmain's strict flag would have forced everydevelop → mainrelease through an admin bypass. Both were fixed locally today byPUTting each ruleset back tostrict=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.jsonEach 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:The JSON ports verbatim with no placeholders:
conditionskeys onref_name(refs/heads/develop|refs/heads/main), not the repo;bypass_actorsuses 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
.github/rulesets/*.jsonamong the verbatim-carry artifacts in "Staying in Sync."diffagainst the committed JSON; correct via a full-payload PUT (GET → change → PUT the whole object — partial PUTs422, already documented in "Rules / Rulesets"). Sketch:scripts/verify-rulesets.shand/or a scheduled CI workflow in derived repos, so drift is caught automatically rather than after it breaks auto-merge.Why now / acceptance
.github/rulesets/{develop,main}.jsoncommitted 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