Skip to content

go-live 1/9: create v1/main, protect it, and give it CI #1824

Description

@cliffhall

Phase 1 of 9 in the v2 go-live runbook — see #1804 (§2, §10). Reversible.

Stand up v1/main as a real, guarded branch before anything is published from it.

Ground truth (verified 2026-07-27)

Thing State
main branch protection (legacy API) none — protection is via rulesets
Ruleset 2749948 "L3 - full protection" active, ~DEFAULT_BRANCH, rules: deletion, non_fast_forward, pull_request
Ruleset 3187937 "L3 - full protection" active, ~DEFAULT_BRANCH, rules: deletion, non_fast_forward, pull_request, required_status_checks (build)
Ruleset 2781134 "build" disabled, ~ALL
Tag rulesets none exist
v1 workflow triggers push: branches: [main], bare pull_request:, release: [published]

⚠️ The two active rulesets target ~DEFAULT_BRANCH, not a branch name. So v1/main inherits nothing — it will be completely unprotected the moment it is created. This phase must create a new ruleset explicitly targeting refs/heads/v1/main; there is no "it's covered by main's rules" shortcut.

Step 1 — create the branch

git fetch origin
git checkout -b v1/main origin/main
git push -u origin v1/main

Verify it sits on the released commit:

git rev-parse origin/v1/main
git rev-list -n1 1.0.0        # must match

Step 2 — give it CI (PR into v1/main)

.github/workflows/main.yml on v1/main, add the branch to the push trigger:

 on:
   push:
     branches:
       - main
+      - v1/main

pull_request: is already unfiltered, so PRs into v1/main are covered with no change — verify, don't edit.

Merge this before applying the ruleset in step 3, so the build check has registered on the branch and the first protected PR can't deadlock on a check that has never run.

Step 3 — branch ruleset for v1/main

Clone the existing ruleset rather than hand-writing it — this preserves the exact bypass_actors (with their actor_ids) and the build check's integration_id, both of which are easy to get wrong by transcription:

gh api repos/modelcontextprotocol/inspector/rulesets/3187937 \
  | jq '{
      name: "v1/main - full protection",
      target, enforcement, bypass_actors,
      conditions: { ref_name: { include: ["refs/heads/v1/main"], exclude: [] } },
      rules
    }' > /tmp/v1-ruleset.json

gh api -X POST repos/modelcontextprotocol/inspector/rulesets --input /tmp/v1-ruleset.json

This carries over: deletion, non_fast_forward, pull_request (1 approval, dismiss stale reviews on push, require code-owner review, require last-push approval), and required_status_checks on context build.

Step 4 — tag ruleset for 1.x

No tag rulesets exist today. Protect the released v1 tags from being moved or deleted, while still allowing creation (phase 2 needs to create 1.0.1):

gh api -X POST repos/modelcontextprotocol/inspector/rulesets --input - <<'JSON'
{
  "name": "v1 release tags",
  "target": "tag",
  "enforcement": "active",
  "conditions": { "ref_name": { "include": ["refs/tags/1.*"], "exclude": [] } },
  "rules": [ { "type": "deletion" }, { "type": "update" } ]
}
JSON

update blocks moving a tag; creation is deliberately not included.

Verification

⚠️ Never test protection with a real git push origin v1/main. You are a
repo admin and the ruleset's bypass_actors include RepositoryRole 5 at
bypass_mode: always — so the push succeeds, proves nothing, and writes to
v1/main. non_fast_forward then blocks a clean undo. Use the API check below.

  • Steps 1–2 — branch exists at 1.0.0 + the workflow commit, build ran on the PR.
  • Step 3 — ruleset 19858989 active. Rules resolve on the branch:
    sh gh api 'repos/modelcontextprotocol/inspector/rules/branches/v1%2Fmain' --jq '.[].type' # → deletion, non_fast_forward, pull_request, required_status_checks
  • Step 4 — tag ruleset 19859230 active. (There is no /rules/tags/
    API — GitHub only exposes /rules/branches/ — so read the ruleset back:)
    sh gh api repos/modelcontextprotocol/inspector/rulesets/19859230 \ --jq '{target, enforcement, include: .conditions.ref_name.include, rules: [.rules[].type]}' # → tag, active, ["refs/tags/1.*"], ["deletion","update"] # creation absent, so phase 2 can still cut 1.0.1

Why the protection is load-bearing

The release environment's Deployment branches and tags is No restriction (verified 2026-07-27). Combined with npm trusted publishing matching on workflow filename only, v1/main's main.yml is authorized to publish the root package name — including to latest — from the moment the branch exists. Anyone who can push to v1/main can publish the v2 package name. This is not a parking branch.

Blocks #1815.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

v2Issues and PRs for v2

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions