Skip to content

feat(ws): make ruleset bypass actors configurable, move php-sdk to a deploy key - #3

Merged
KaiSchwarz-cnic merged 3 commits into
mainfrom
RSRMID-2994/release-push-deploy-keys
Aug 26, 2026
Merged

feat(ws): make ruleset bypass actors configurable, move php-sdk to a deploy key#3
KaiSchwarz-cnic merged 3 commits into
mainfrom
RSRMID-2994/release-push-deploy-keys

Conversation

@KaiSchwarz-cnic

Copy link
Copy Markdown
Contributor

RSRMID-2994 — the engine change, the new profile, and the first repository to move.

The problem

Ten repositories carry releases-to-branch, whose only content is RULESET_ENABLED=false. @semantic-release/git pushes the release commit straight to the default branch, a ruleset would reject it, and scripts/repo-settings.sh had no way to express a bypass — the ruleset it built had no bypass_actors at all. So those ten repositories came out of centralisation with weaker protection than they went in with, and the push identity is RTLDEV_MW_CI_TOKEN, a personal account's PAT.

Three commits

feat(ws): make ruleset bypass actors configurableRULESET_BYPASS_ACTORS, format <actor_type>:<actor_id>:<bypass_mode>, comma-separated. Empty in _baseline.conf: no bypass unless a profile asks. actor_id is left empty for the types GitHub treats as a class rather than as one actor — DeployKey requires actor_id: null.

Two details worth reviewing:

  • Parsed in bash, not in jq, so a malformed entry dies with the entry quoted. A typo that silently produced no bypass would reject the push the bypass exists to allow; one that produced the wrong actor would hand the bypass to someone else. Four malformed shapes are refused: missing field, unknown bypass_mode, non-numeric actor_id, and @unmanaged.
  • --check now compares the bypass list, where before it only asked whether the ruleset existed. A bypass actor added by hand in the web UI is drift of exactly the shape that makes every other rule in the ruleset decorative, and it was invisible. The list is fetched per ruleset because GET /rulesets returns a summary — bypass_actors appears only on GET /rulesets/{id}.

fix(repos): semantic-release-plugins does not push release commits — it carried releases-to-branch and does not use @semantic-release/git, so it was giving up branch protection for an exemption it never needed. Nine left to migrate, not ten.

feat(repos): move php-sdk's release push to a deploy key — php-sdk takes releases-via-deploykey. It is the pilot because it is the one repository whose REQUIRED_CHECKS are filled in, so it is the only one where the end state (ruleset with real required checks, classic protection removed) is reachable now rather than after RSRMID-2991. Its release also publishes docs, but to gh-pages, not the default branch — so unlike devcontainer-features it needs no second push moved.

Verification

shellcheck --severity=warning scripts/*.sh .devcontainer/*.sh   # clean
npx prettier@3 --check .                                        # clean
./scripts/org-settings.sh --resolve php-sdk                     # RULESET_BYPASS_ACTORS="DeployKey::always" wins over the baseline ""
./scripts/org-settings.sh --check semantic-release-plugins php-sdk

The --check run reports the expected drift and nothing else: ruleset absent on both, plus the pre-existing "classic branch protection is ALSO present" note on php-sdk. The bypass comparison was exercised against a real ruleset (semantic-release-plugins' orphan main, bypass_actors: []) in both directions — match and drift — and the canonical sort was checked with two actors given in reverse order with stray whitespace.

This PR changes nothing on GitHub by itself

The trait switch is inert until scripts/org-settings.sh --apply php-sdk runs, which is a manual step; repo-settings-drift.yml only checks. Until that apply, the weekly drift job will report php-sdk's ruleset as absent.

Order

Merge last, after:

  1. RTLDEV_MW_CI_SSH_KEY is set on php-sdk (a deploy key titled semantic-release, id 161381923, is already registered)
  2. ci(php-sdk-release): provide the deploy key for the release push rtldev-middleware-shareable-workflows#104 — puts the key on the runner
  3. ci(release): push the release commit over SSH with a deploy key rtldev-middleware-php-sdk#326 — points repositoryUrl at SSH

Then scripts/org-settings.sh --apply php-sdk creates the ruleset, and --apply semantic-release-plugins picks up rung 0.

Still to do, not in this PR

  • Rung 0's orphan ruleset on semantic-release-plugins (id 18311775, named main, conditions.ref_name.include: [] so it matches no branch) has to be deleted by hand — the engine only knows the ruleset it is configured to own.
  • P8: verify a real release pushed over SSH, with DEBUG: semantic-release:get-git-auth-url set for the pilot run.
  • P9: remove php-sdk's classic branch protection, but only after P8 is green — classic protection and rulesets apply as a union, so removing it early lowers protection.
  • _profile-releases-to-branch.conf is deleted, and the register's trait comment trimmed, once no repository carries it.

🤖 Generated with Claude Code

The branch-protection block built a ruleset with no bypass_actors, so no
configuration could express one. That is why ten repositories carry
releases-to-branch and have RULESET_ENABLED=false: @semantic-release/git pushes
the release commit straight to the default branch, a ruleset would reject it,
and there was no way to let exactly that push past.

RULESET_BYPASS_ACTORS takes <actor_type>:<actor_id>:<bypass_mode>, comma
separated, with actor_id empty for the types GitHub treats as a class rather
than as one actor — DeployKey requires actor_id null, because the bypass is
granted to "a deploy key of this repository", not to one key. The baseline is
empty: no bypass unless a profile asks for one.

Parsed in bash rather than in jq so a malformed entry dies with the entry
quoted. A typo that silently produced no bypass would reject the push the
bypass exists to allow; one that produced the wrong actor would hand the
bypass to someone else.

--check compares the bypass list as well, not just the ruleset's existence. An
actor added by hand in the web UI is drift of exactly the shape that makes
every other rule in the ruleset decorative, and it was invisible. The list is
fetched per ruleset because GET /rulesets returns a summary — bypass_actors
appears only on GET /rulesets/{id}.

_profile-releases-via-deploykey.conf is the one consumer: one line, identical
for every repository that moves, so no repository gets a bypass of its own in
a file nobody else reads.
The repository carried releases-to-branch, which withholds branch protection
because @semantic-release/git pushes the release commit to the default branch.
It does not use that plugin, so it was giving up branch protection in exchange
for an exemption it never needed.

Nine repositories are left to migrate, not ten.
php-sdk is the pilot for RSRMID-2994. It is the one repository in the
organisation whose REQUIRED_CHECKS are filled in, so it is the only one where
the end state — ruleset with real required checks, classic protection gone — is
reachable now rather than after RSRMID-2991.

Its release also publishes documentation, but to gh-pages, not to the default
branch. The ruleset targets ~DEFAULT_BRANCH only, so unlike
devcontainer-features this repository needs no second push moved to SSH.

The trait switch is inert until scripts/org-settings.sh --apply php-sdk runs.
Rollback is this line back to releases-to-branch and another --apply; the
deploy key and the secret can stay, they do nothing without the ruleset.
@KaiSchwarz-cnic
KaiSchwarz-cnic merged commit 0a86e95 into main Aug 26, 2026
5 checks passed
@KaiSchwarz-cnic
KaiSchwarz-cnic deleted the RSRMID-2994/release-push-deploy-keys branch August 26, 2026 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant