coderifts 6.0.2 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 6.1.0
4
+
5
+ ### Added
6
+
7
+ - **enforce --check now reads GitHub rulesets.** The evaluator existed with zero production call sites: a ruleset-governed repository reported required_check NOT_VERIFIED while its merges were gated. Rulesets are readable where classic protection is not (200 vs 401), so required_check can now reach VERIFIED on an unreadable classic response.
8
+
3
9
  ## 6.0.2
4
10
 
5
11
  ### Fixed
package/dist/cli.js CHANGED
@@ -3028,7 +3028,7 @@ var require_package = __commonJS({
3028
3028
  "package.json"(exports2, module2) {
3029
3029
  module2.exports = {
3030
3030
  name: "coderifts",
3031
- version: "6.0.2",
3031
+ version: "6.1.0",
3032
3032
  description: "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
3033
3033
  author: "CodeRifts <hello@coderifts.com>",
3034
3034
  license: "MIT",
@@ -254196,7 +254196,9 @@ var require_github_enforcement = __commonJS({
254196
254196
  });
254197
254197
  function verifiedRequiresBinding(layer2) {
254198
254198
  if (!layer2 || layer2.status !== STATUS.VERIFIED) return true;
254199
- if (layer2.id === "required_check") return layer2.reason_code === REASON.ISSUER_BOUND;
254199
+ if (layer2.id === "required_check") {
254200
+ return layer2.reason_code === REASON.ISSUER_BOUND || layer2.reason_code === REASON.RULESET_ISSUER_BOUND;
254201
+ }
254200
254202
  if (layer2.id === "workflow_contract_gate" || layer2.id === "workflow_deploy_gate") {
254201
254203
  return layer2.reason_code === REASON.ACTIVE_JOB;
254202
254204
  }
@@ -254648,15 +254650,48 @@ var require_github_enforcement = __commonJS({
254648
254650
  environmentRead,
254649
254651
  environmentName,
254650
254652
  local,
254651
- auth
254653
+ auth,
254654
+ rulesetsRead = null,
254655
+ branch = null
254652
254656
  } = {}) {
254653
254657
  const layers = [];
254654
254658
  const localFiles = local || { contractGateFiles: [], deployGateFiles: [] };
254659
+ const rulesetVerdict = evaluateRuleset(rulesetsRead, branch);
254660
+ const rulesetUpgrade = (classicLayerFields) => {
254661
+ if (rulesetVerdict.status !== STATUS.VERIFIED) return null;
254662
+ return {
254663
+ status: STATUS.VERIFIED,
254664
+ reason: `${rulesetVerdict.reason} Classic branch protection did not establish this: ${classicLayerFields.reason}`,
254665
+ reason_code: rulesetVerdict.reason_code,
254666
+ endpoint: "GET /repos/{owner}/{repo}/rulesets",
254667
+ app_id: rulesetVerdict.app_id,
254668
+ ruleset_names: rulesetVerdict.ruleset_names,
254669
+ surfaces_inspected: ["classic_branch_protection", "rulesets"]
254670
+ };
254671
+ };
254672
+ const pushRequiredCheck = (classicFields) => {
254673
+ const up = rulesetUpgrade(classicFields);
254674
+ layers.push(layer("required_check", up ? up.status : classicFields.status, {
254675
+ ...up || classicFields,
254676
+ ruleset_status: rulesetVerdict.status,
254677
+ ruleset_reason_code: rulesetVerdict.reason_code,
254678
+ surfaces_inspected: [
254679
+ "classic_branch_protection",
254680
+ rulesetsRead == null ? "rulesets:unread" : "rulesets"
254681
+ ]
254682
+ }));
254683
+ };
254655
254684
  const noAuth = !auth || auth.source === "none" || !auth.ok;
254656
254685
  const prot = protectionRead || null;
254657
254686
  if (noAuth && !prot) {
254658
254687
  const why = auth && auth.reason || NO_TOKEN_INSTRUCTION;
254659
- for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
254688
+ pushRequiredCheck({
254689
+ status: STATUS.UNVERIFIABLE,
254690
+ reason: why,
254691
+ endpoint: null,
254692
+ missing_scope: APP_INSTALLATION.missing_scope
254693
+ });
254694
+ for (const id of ["enforce_admins", "strict_status_checks"]) {
254660
254695
  layers.push(layer(id, STATUS.UNVERIFIABLE, {
254661
254696
  reason: why,
254662
254697
  endpoint: null,
@@ -254665,13 +254700,21 @@ var require_github_enforcement = __commonJS({
254665
254700
  }
254666
254701
  } else if (!prot) {
254667
254702
  const why = "branch protection was not queried";
254668
- for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
254703
+ pushRequiredCheck({ status: STATUS.UNVERIFIABLE, reason: why, endpoint: null });
254704
+ for (const id of ["enforce_admins", "strict_status_checks"]) {
254669
254705
  layers.push(layer(id, STATUS.UNVERIFIABLE, { reason: why, endpoint: null }));
254670
254706
  }
254671
254707
  } else if (prot.status === 403) {
254672
254708
  const why = "GitHub HTTP 403 \u2014 missing scope administration:read (branch protection). Grant Administration: Read on the token, or use a repo-admin token.";
254673
254709
  const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
254674
- for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
254710
+ pushRequiredCheck({
254711
+ status: STATUS.UNVERIFIABLE,
254712
+ reason: why,
254713
+ endpoint,
254714
+ missing_scope: "administration:read",
254715
+ http_status: 403
254716
+ });
254717
+ for (const id of ["enforce_admins", "strict_status_checks"]) {
254675
254718
  layers.push(layer(id, STATUS.UNVERIFIABLE, {
254676
254719
  reason: why,
254677
254720
  endpoint,
@@ -254681,11 +254724,12 @@ var require_github_enforcement = __commonJS({
254681
254724
  }
254682
254725
  } else if (prot.status === 404) {
254683
254726
  const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
254684
- layers.push(layer("required_check", STATUS.NOT_VERIFIED, {
254727
+ pushRequiredCheck({
254728
+ status: STATUS.NOT_VERIFIED,
254685
254729
  reason: `classic branch protection is absent \u2014 required check "${CHECK_NAME}" is not required`,
254686
254730
  endpoint,
254687
254731
  http_status: 404
254688
- }));
254732
+ });
254689
254733
  layers.push(layer("enforce_admins", STATUS.NOT_VERIFIED, {
254690
254734
  reason: "classic branch protection is absent \u2014 administrators are unrestricted",
254691
254735
  endpoint,
@@ -254699,7 +254743,13 @@ var require_github_enforcement = __commonJS({
254699
254743
  } else if (prot.status >= 400 || prot.status === 0) {
254700
254744
  const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
254701
254745
  const why = `GitHub HTTP ${prot.status || 0} reading branch protection \u2014 cannot verify`;
254702
- for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
254746
+ pushRequiredCheck({
254747
+ status: STATUS.UNVERIFIABLE,
254748
+ reason: redactSecrets(why),
254749
+ endpoint,
254750
+ http_status: prot.status
254751
+ });
254752
+ for (const id of ["enforce_admins", "strict_status_checks"]) {
254703
254753
  layers.push(layer(id, STATUS.UNVERIFIABLE, {
254704
254754
  reason: redactSecrets(why),
254705
254755
  endpoint,
@@ -254710,14 +254760,15 @@ var require_github_enforcement = __commonJS({
254710
254760
  const protection = prot.body && typeof prot.body === "object" ? prot.body : {};
254711
254761
  const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
254712
254762
  const required = evaluateRequiredCheck(protection);
254713
- layers.push(layer("required_check", required.status, {
254763
+ pushRequiredCheck({
254764
+ status: required.status,
254714
254765
  reason: required.reason,
254715
254766
  reason_code: required.reason_code,
254716
254767
  endpoint,
254717
254768
  required_contexts: required.required_contexts,
254718
254769
  app_id: required.app_id,
254719
254770
  ...required.expected_app_id != null ? { expected_app_id: required.expected_app_id } : {}
254720
- }));
254771
+ });
254721
254772
  const admins = enforceAdminsEnabled(protection);
254722
254773
  layers.push(layer("enforce_admins", admins ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
254723
254774
  reason: admins ? "enforce_admins.enabled is true \u2014 administrators cannot bypass required checks" : "enforce_admins.enabled is false \u2014 repository administrators can bypass required checks",
@@ -254885,11 +254936,27 @@ var require_github_enforcement = __commonJS({
254885
254936
  const protPath = resolvedBranch ? `repos/${owner}/${repo}/branches/${encodeURIComponent(resolvedBranch)}/protection` : protectionPath;
254886
254937
  const protectionRead = client && protPath ? await client.get(protPath) : null;
254887
254938
  const environmentRead = client && envPath ? await client.get(envPath) : null;
254939
+ let rulesetsRead = null;
254940
+ if (client) {
254941
+ const list = await client.get(`repos/${owner}/${repo}/rulesets`);
254942
+ if (list && list.status === 200 && Array.isArray(list.body)) {
254943
+ const detailed = [];
254944
+ let anyDetailFailed = false;
254945
+ for (const rs of list.body) {
254946
+ if (!rs || rs.id == null) continue;
254947
+ const one = await client.get(`repos/${owner}/${repo}/rulesets/${rs.id}`);
254948
+ if (one && one.status === 200 && one.body) detailed.push(one.body);
254949
+ else anyDetailFailed = true;
254950
+ }
254951
+ rulesetsRead = anyDetailFailed ? null : detailed;
254952
+ }
254953
+ }
254888
254954
  return {
254889
254955
  branch: resolvedBranch,
254890
254956
  repoRead,
254891
254957
  protectionRead,
254892
- environmentRead
254958
+ environmentRead,
254959
+ rulesetsRead
254893
254960
  };
254894
254961
  }
254895
254962
  module2.exports = {
@@ -255853,6 +255920,7 @@ var require_enforce_check = __commonJS({
255853
255920
  let branch = options.branch ? String(options.branch).trim() : null;
255854
255921
  let protectionRead = null;
255855
255922
  let environmentRead = null;
255923
+ let rulesetsRead = null;
255856
255924
  if (auth.ok) {
255857
255925
  const client = deps.githubClient || createGitHubHttpClient({
255858
255926
  token: auth.token,
@@ -255868,13 +255936,18 @@ var require_enforce_check = __commonJS({
255868
255936
  branch = queried.branch || branch;
255869
255937
  protectionRead = queried.protectionRead;
255870
255938
  environmentRead = queried.environmentRead;
255939
+ rulesetsRead = queried.rulesetsRead;
255871
255940
  }
255872
255941
  const evaluated = evaluateLayers({
255873
255942
  protectionRead,
255874
255943
  environmentRead,
255875
255944
  environmentName,
255876
255945
  local,
255877
- auth
255946
+ auth,
255947
+ // roadmap 1052a — the ruleset surface, which reads without a credential and can establish
255948
+ // required_check where classic branch protection is unreadable.
255949
+ rulesetsRead,
255950
+ branch
255878
255951
  });
255879
255952
  const report = {
255880
255953
  command: "enforce --check",
@@ -255894,7 +255967,9 @@ var require_enforce_check = __commonJS({
255894
255967
  // response or to our grading of it.
255895
255968
  responses: {
255896
255969
  protection: responseDigest(protectionRead),
255897
- environment: responseDigest(environmentRead)
255970
+ environment: responseDigest(environmentRead),
255971
+ // null here means NOT READ, which is different from an empty ruleset list.
255972
+ rulesets: rulesetsRead == null ? null : { count: rulesetsRead.length }
255898
255973
  },
255899
255974
  ...evaluated
255900
255975
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coderifts",
3
- "version": "6.0.2",
3
+ "version": "6.1.0",
4
4
  "description": "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
5
5
  "author": "CodeRifts <hello@coderifts.com>",
6
6
  "license": "MIT",