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.

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