Skip to content

ci(repo): fail CI until main's rules require every pull-request check - #78

Merged
ryanleecode merged 7 commits into
mainfrom
gates/u3-rules-check
Oct 8, 2026
Merged

ryanleecode merged 7 commits into
mainfrom
gates/u3-rules-check

Conversation

@systemfsoftware-maker

@systemfsoftware-maker systemfsoftware-maker commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Units U1, U3 and U4 of docs/plans/2026-10-08-0705-feat-gates-bind-and-inline-suppression-plan.md (C1, R22, AE10). Conductor rulings 13, 14 and 15.

Change

  • scripts/check-branch-rules.ts: a Deno script with the shebang --allow-net=api.github.com --allow-env=GITHUB_TOKEN,GITHUB_REPOSITORY, following check-sfs-sources.ts. The shell reads GET /repos/{owner}/{repo}/rules/branches/main with GITHUB_TOKEN and logs the HTTP status and the active rule types. Effect Schema decodes the response, with effect taken from the workspace's installed node_modules. The pure branchRulesVerdict(rules, requiredChecks) returns the required checks that no required_status_checks rule requires, as data. The script exits 1 when any are missing, when the call fails, or when the response doesn't decode.
  • scripts/check-branch-rules.test.ts: one property and two hand-written refusals. Expected values come from R22 and GitHub's documented "Get rules for a branch" response, not from the code's output.
    • Property: over every subset of the 10 required names (all 1,024, enumerated), the verdict names exactly the omitted subset, and the empty subset passes. This holds whether the enforced names sit in one status-check rule or are split across two, and with deletion, non_fast_forward, pull_request and an unrelated status check mixed in.
    • The generated status checks carry an integer integration_id, integration_id: null or no integration_id at all, so the schema must accept all three (ruling 16, F1). A ruleset created from gates.json through the REST API pins no app, and the decode can't strand it red. Sabotage: reverting the schema to integer-only turns the property red.
    • Refusal: no rules at all names every required check.
    • Refusal: today's template rules (deletion + non_fast_forward + pull_request) name every required check.
  • .github/workflows/ci.yml: one rules job on ubuntu-latest with permissions: contents: read. It installs the workspace through the dev-shell action's default offline pnpm bootstrap from the Nix pnpm store, so node_modules/effect exists, then runs pnpm check:branch-rules. It has no continue-on-error and no if:. (On f5a8352 the job skipped the install and failed with Could not resolve "effect" before it made the request; adfa340 fixes that.)
  • package.json:
    • Adds check:branch-rules (sandbox egress: api.github.com, jsr.io).
    • Adds effect to the root devDependencies as catalog:, which is effect: 4.0.1 in pnpm-workspace.yaml. The root package owns scripts/, so pnpm installs effect there from the existing lockfile store. pnpm-lock.yaml gains only that importer entry; effect@4.0.1 was already locked.
    • No sandbox gains a host, and test:scripts gains no network permission.
  • scripts/deno.json: "nodeModulesDir": "manual", so Deno resolves effect from the existing node_modules and downloads nothing from npm. There's no npm: import entry, and scripts/deno.lock is unchanged from main.
    • Cold-cache proof: run with DENO_DIR set to an empty directory, pnpm test:scripts downloads only jsr.io modules and passes (ok | 9 passed | 0 failed). Nothing in the output mentions registry.npmjs.org.
    • Why: the first CI run's check (test) failed with Failed caching npm package 'effect@4.0.1' / unsuccessful tunnel, because Deno tried to download effect from the npm registry inside the test sandbox.
  • .github/rulesets/gates.json: the "gates" repository ruleset for refs/heads/main. It has enforcement: active, bypass_actors: [], pull_request, and required_status_checks with exactly the 10 names below, plus strict_required_status_checks_policy: true.
  • README.md: Getting Started gains "5. Make the Gates Block Merges", between "Run It Locally" and "Deploy". It gives the one install command, gh api -X POST repos/{owner}/{repo}/rulesets --input .github/rulesets/gates.json, and says the rules check fails until it runs.

Judgment surfaces changed (GATE1, conductor Q7): .github/workflows/ci.yml, .github/rulesets/gates.json, package.json (scripts, plus the effect dev dependency), scripts/check-branch-rules.ts, scripts/deno.json.

Required checks (item 2)

These are the check names exactly as GitHub reported them on #77's head 9b4e918 (CI run 37840842636, Commitlint run 37840842631, Changeset Check run 37840843669), plus rules:

  1. check (format)
  2. check (lint)
  3. check (typecheck)
  4. check (test)
  5. check (dist)
  6. check (sfs-sources)
  7. journeys
  8. lint PR commits
  9. changeset · shared tooling / a publishable-package change needs an intent
  10. rules

U1: can GITHUB_TOKEN read branch rules?

Answered by this PR's first rules run, with no throwaway workflow or scratch branch: CI run 37845459980, job rules (113545095569). The workflow's GITHUB_TOKEN ran with permissions: contents: read.

check-branch-rules: GET /repos/systemfsoftware/effect-endgame-starter-kit/rules/branches/main -> HTTP 200
check-branch-rules: active rule types: deletion, non_fast_forward, pull_request
check-branch-rules: main's active rules do not require 10 of the 10 checks a pull request must pass; install .github/rulesets/gates.json (README, Getting Started):
  • Status: HTTP 200.
  • Rule types: deletion, non_fast_forward, pull_request. These match the 2026-10-08 read with a personal token.
  • The job exits 1, naming all 10 required checks. That is AE10 "before".
  • Not checked: whether GET rulesets/{id} returns bypass_actors to GITHUB_TOKEN. Ruling 4 keeps bypass actors out of the check's reach.

Predicate

  • AE10 "before": on this PR, the rules job fails against the template's current rules, and every other check is green. That red rules job is the expected result.
  • AE10 "after": the conductor installs gates.json on the template, then re-runs rules. This PR doesn't install it.

Must not count

  • A rules check that passes when some ruleset exists but doesn't require the named checks. The verdict reads only required_status_checks contexts. The template's current ruleset (three other rule types) is refused, both in the test and in the PR run.
  • rules missing from its own required list. It's in both REQUIRED_CHECKS and gates.json.
  • An evaluate or disabled ruleset counted as present. The endpoint returns only active rules (GitHub docs, "Get rules for a branch").
  • Any token or secret other than GITHUB_TOKEN.
  • Any GitHub settings or ruleset change made by me. None was made.
  • continue-on-error or if: making rules green, --no-verify, or a weakened threshold. None is used.

Evidence

  • Locally, in the dev shell: pnpm format:check, pnpm lint, pnpm typecheck and pnpm test (includes test:scripts: ok | 9 passed | 0 failed) all exit 0.
  • Sabotage, on throwaway copies deleted afterwards:
    • Making the verdict always return [] turns all three tests red.
    • Making any non-empty rule list count as compliant turns the property and the template-rules refusal red.
  • No stryker run locally.

Note

ci.yml is also changed by #77, and this branch is cut from main as ruling 13 directs. #77 drops the push trigger at the top of the file; this PR appends the rules job at the end. git merge-tree origin/gates/u2-deploy-waits HEAD reports no conflict.

systemfsoftware-maker and others added 7 commits October 8, 2026 21:14
U3 and U4 of the gates-bind plan (R22, AE10). A rules job in ci.yml reads
GET /repos/{owner}/{repo}/rules/branches/main with the workflow's
GITHUB_TOKEN, decodes it with Effect Schema and fails while the active
rules do not require the six check legs, journeys, lint PR commits, the
changeset check and rules itself. .github/rulesets/gates.json is that
ruleset (active, no bypass actors, up-to-date branches required) and the
README's new Getting Started step installs it, per conductor rulings 13
and 14
…o tests

pnpm test and pnpm gate:tasks wrap test:scripts in an outer sandbox that
allowed only jsr.io, so Deno could not fetch npm:effect@4.0.1 on a cold
cache: egress to registry.npmjs.org:443 refused, Failed caching npm
package 'effect@4.0.1'. Both outer sandboxes now allow registry.npmjs.org,
the one host test:scripts already declares
…o scripts

Deno now resolves effect through nodeModulesDir manual from the root
package's installed effect (catalog, 4.0.1, already in pnpm-lock.yaml),
so test:scripts downloads nothing from the npm registry. The npm:effect
import entry and its deno.lock lines are gone, and every sandbox is back
to its earlier hosts, per conductor ruling 15
check-branch-rules.ts now takes effect from node_modules, and the rules
job skipped the install, so Deno failed with 'Could not resolve effect'
on run 37849660110. The dev-shell step's default install is offline from
the Nix pnpm store
A ruleset created from gates.json through the REST API pins no app, so
a required status check may come back with integration_id null. The
decode refused that and would have kept rules red with the ruleset
installed. The property now generates integer, null and absent ids, per
conductor ruling 16
@ryanleecode
ryanleecode merged commit 408d9f3 into main Oct 8, 2026
12 checks passed
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.

2 participants