The App is installed. The check is not a gate yet.
Installing the GitHub App posts CodeRifts / issuer. Until branch protection requires that check and the reader job name CodeRifts / contract-gate, a red result does not stop a merge. The check stays advisory. The reader is the required check; the App is the second, issuer-side context.
Require the check
Two required contexts. Miss one and the merge is not the gate. Measured 2026-10-11 on coderifts/demo: ruleset 22074842 alone, enforcement active, bypass_actors empty, required checks CodeRifts / issuer (2860592) and CodeRifts / contract-gate (15368). Classic branch protection is deleted: GET /repos/coderifts/demo/branches/main/protection answers 404 Branch not protected. The template job name is the context.
- Reader. The context is
CodeRifts / contract-gate, GitHub Actions, app id15368. The template job name is the context. The Actions issuer is shared, so the app id does not name which workflow ran. - Issuer. The context is
CodeRifts / issuer, the CodeRifts App, app id2860592(GET /apps/coderifts). The reader is the required check; the App is the second, issuer-side context. - Bypass empty. On classic branch protection,
enforce_adminsis on. In the UI: Settings → Branches → the rule for the default branch → Include administrators. On a ruleset,bypass_actorsis empty. The reader may be on either surface. Measured 2026-10-11 oncoderifts/demo: ruleset22074842is active,bypass_actorsis empty, and classic branch protection is deleted (GET /repos/coderifts/demo/branches/main/protectionanswers404 Branch not protected).enforce_adminsis absent from the ruleset response. An admin override is refused with “2 of 2 required status checks are failing.”
Replace the old App-bound CodeRifts / contract-gate row with CodeRifts / issuer. Left in place, that row stays expected. The checks[] form is what stores app_id. The legacy contexts[] form cannot.
gh api -X PUT repos/OWNER/REPO/branches/main/protection \
-H "Accept: application/vnd.github+json" --input - <<'JSON'
{
"required_status_checks": {
"strict": false,
"checks": [
{ "context": "CodeRifts / contract-gate", "app_id": 15368 },
{ "context": "CodeRifts / issuer", "app_id": 2860592 }
]
},
"enforce_admins": true,
"required_pull_request_reviews": null,
"restrictions": null
}
JSON
Read it back. An app_id of null means the binding did not take. strict is left false here on purpose: the check is the gate, not a rebase. This call replaces the protection object; read the current rule first if the branch already has other required checks. Measured 2026-10-11 on coderifts/demo, that classic protection call is not the live surface: GET /repos/coderifts/demo/branches/main/protection answers 404 Branch not protected. The live surface is ruleset 22074842.
Open a pull request that changes a contract
Without a grant the check is red. A config-only change is green. The public examples are demo pull request #4 (a contract change) and demo pull request #22 (config only).
the reader verifies the receipt offline; the demo's PR #4 is red on both contexts
Verify one decision yourself
Take the receipt off the check and verify it on /verify/. The page checks the signature offline. A green check on GitHub is not that verification.
.coderifts.yml names the contract artifacts to track. Policy keys do not move the grant. A grant is the signed receipt for one operation on one change, not a key in that file. contract_artifacts: none suppresses a comment when a repository has no contract artifact. It does not change what the check concludes (src/config-parser.js).
Need help? Contact hello@coderifts.com