coderifts 6.0.1 → 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 +13 -0
- package/dist/cli.js +136 -16
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
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
|
+
|
|
9
|
+
## 6.0.2
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **init --agents wrote 8 of 9 embedded files.** The skill file joined the single source on 2026-08-25 but was never added to EXTRA_RULES_ON_ALL, a second hand-maintained list. Every release since shipped an installer that under-writes, and agent-setup --check was correctly reporting DRIFT the whole time.
|
|
14
|
+
- **The strict workflow pins actions/checkout too.** It runs first and fetches the code every later step reads; the least pinned step in the strict chain was the most load-bearing.
|
|
15
|
+
|
|
3
16
|
## 6.0.1
|
|
4
17
|
|
|
5
18
|
### Changed
|
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
|
|
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",
|
|
@@ -227483,6 +227483,25 @@ var require_gate_pin = __commonJS({
|
|
|
227483
227483
|
}
|
|
227484
227484
|
});
|
|
227485
227485
|
|
|
227486
|
+
// src/generated/action-pins.json
|
|
227487
|
+
var require_action_pins = __commonJS({
|
|
227488
|
+
"src/generated/action-pins.json"(exports2, module2) {
|
|
227489
|
+
module2.exports = {
|
|
227490
|
+
actions: [
|
|
227491
|
+
{
|
|
227492
|
+
action: "actions/checkout",
|
|
227493
|
+
sha: "11d5960a326750d5838078e36cf38b85af677262",
|
|
227494
|
+
tag: "v4",
|
|
227495
|
+
exact_tag: "v4.4.0",
|
|
227496
|
+
resolved_from: "https://github.com/actions/checkout refs/tags/v4",
|
|
227497
|
+
resolved_at: "2026-08-27T13:27:52Z"
|
|
227498
|
+
}
|
|
227499
|
+
],
|
|
227500
|
+
note: "STRICT template pins these third-party actions to full commit SHAs (only a full SHA is immutable). The DEFAULT template deliberately stays on moving tags. A SHA pin does not receive upstream security fixes until someone bumps it \u2014 Dependabot raises that PR, and a pin nobody bumps is worse than a tag because it rots silently. Re-run scripts/generate-action-pins.js."
|
|
227501
|
+
};
|
|
227502
|
+
}
|
|
227503
|
+
});
|
|
227504
|
+
|
|
227486
227505
|
// src/contract-gate-workflow.js
|
|
227487
227506
|
var require_contract_gate_workflow = __commonJS({
|
|
227488
227507
|
"src/contract-gate-workflow.js"(exports2, module2) {
|
|
@@ -227492,6 +227511,17 @@ var require_contract_gate_workflow = __commonJS({
|
|
|
227492
227511
|
var WORKFLOW_REL = ".github/workflows/coderifts.yml";
|
|
227493
227512
|
var GATE_ACTION = "coderifts/contract-gate@v0";
|
|
227494
227513
|
var GATE_PIN = require_gate_pin();
|
|
227514
|
+
var ACTION_PINS = require_action_pins();
|
|
227515
|
+
function pinnedActionRef(action) {
|
|
227516
|
+
const entry = (ACTION_PINS.actions || []).find((a) => a && a.action === action);
|
|
227517
|
+
if (!entry || !/^[0-9a-f]{40}$/.test(String(entry.sha || ""))) {
|
|
227518
|
+
throw new Error(
|
|
227519
|
+
`contract-gate-workflow: no usable SHA pin for ${action} in src/generated/action-pins.json \u2014 run scripts/generate-action-pins.js. Refusing to emit an unpinned STRICT workflow.`
|
|
227520
|
+
);
|
|
227521
|
+
}
|
|
227522
|
+
return `${action}@${entry.sha} # ${entry.exact_tag || entry.tag}`;
|
|
227523
|
+
}
|
|
227524
|
+
var CHECKOUT_STRICT = pinnedActionRef("actions/checkout");
|
|
227495
227525
|
if (typeof GATE_PIN.sha !== "string" || !/^[0-9a-f]{40}$/.test(GATE_PIN.sha)) {
|
|
227496
227526
|
throw new Error(
|
|
227497
227527
|
"contract-gate-workflow: generated/gate-pin.json carries no valid 40-hex sha \u2014 re-run scripts/generate-gate-pin.js. Refusing to emit an unpinned STRICT workflow."
|
|
@@ -227526,6 +227556,10 @@ jobs:
|
|
|
227526
227556
|
contract-gate:
|
|
227527
227557
|
runs-on: ubuntu-latest
|
|
227528
227558
|
steps:
|
|
227559
|
+
# actions/checkout stays on the MOVING TAG @v4 here, deliberately \u2014 the same trade this
|
|
227560
|
+
# template makes for the gate itself. You get upstream fixes without editing this file, and
|
|
227561
|
+
# you accept that the bytes can change under you. \`coderifts init --agents --strict\` pins a
|
|
227562
|
+
# full commit SHA for both. Do not "fix" this to a SHA and do not "fix" strict back to a tag.
|
|
227529
227563
|
- uses: actions/checkout@v4
|
|
227530
227564
|
with:
|
|
227531
227565
|
# REQUIRED: the gate derives the change set from git diff base...head.
|
|
@@ -227582,7 +227616,14 @@ jobs:
|
|
|
227582
227616
|
contract-gate:
|
|
227583
227617
|
runs-on: ubuntu-latest
|
|
227584
227618
|
steps:
|
|
227585
|
-
|
|
227619
|
+
# PINNED, like the gate below it. \`@v4\` is a tag GitHub moves on every release of the
|
|
227620
|
+
# action, so it is not a pin \u2014 and checkout runs FIRST, fetching the code every later step
|
|
227621
|
+
# reads. A strict chain whose first step can change under it is not strict.
|
|
227622
|
+
# MAINTENANCE, stated because a pin has a cost: a SHA-pinned action receives no upstream
|
|
227623
|
+
# fixes, security included, until someone bumps it. Dependabot understands this form and
|
|
227624
|
+
# raises that PR. WITHOUT Dependabot the obligation is yours, and a pin nobody bumps is worse
|
|
227625
|
+
# than a tag, because a tag rots visibly and a pin rots silently.
|
|
227626
|
+
- uses: ${CHECKOUT_STRICT}
|
|
227586
227627
|
with:
|
|
227587
227628
|
# REQUIRED: the gate derives the change set from git diff base...head.
|
|
227588
227629
|
fetch-depth: 0
|
|
@@ -227666,6 +227707,9 @@ jobs:
|
|
|
227666
227707
|
GATE_ACTION,
|
|
227667
227708
|
GATE_ACTION_STRICT,
|
|
227668
227709
|
GATE_PIN,
|
|
227710
|
+
ACTION_PINS,
|
|
227711
|
+
CHECKOUT_STRICT,
|
|
227712
|
+
pinnedActionRef,
|
|
227669
227713
|
ENFORCEMENT_DOC,
|
|
227670
227714
|
writeContractGateWorkflow,
|
|
227671
227715
|
workflowPresent
|
|
@@ -227722,7 +227766,8 @@ var require_init_agents = __commonJS({
|
|
|
227722
227766
|
var EXTRA_RULES_ON_ALL = Object.freeze([
|
|
227723
227767
|
".github/copilot-instructions.md",
|
|
227724
227768
|
"coderifts-langgraph-policy.js",
|
|
227725
|
-
"openai-agent-instructions.md"
|
|
227769
|
+
"openai-agent-instructions.md",
|
|
227770
|
+
"skills/api-governance/SKILL.md"
|
|
227726
227771
|
]);
|
|
227727
227772
|
var NEXT_MANUAL_STEP_LINES = Object.freeze([
|
|
227728
227773
|
" Next manual step",
|
|
@@ -254151,7 +254196,9 @@ var require_github_enforcement = __commonJS({
|
|
|
254151
254196
|
});
|
|
254152
254197
|
function verifiedRequiresBinding(layer2) {
|
|
254153
254198
|
if (!layer2 || layer2.status !== STATUS.VERIFIED) return true;
|
|
254154
|
-
if (layer2.id === "required_check")
|
|
254199
|
+
if (layer2.id === "required_check") {
|
|
254200
|
+
return layer2.reason_code === REASON.ISSUER_BOUND || layer2.reason_code === REASON.RULESET_ISSUER_BOUND;
|
|
254201
|
+
}
|
|
254155
254202
|
if (layer2.id === "workflow_contract_gate" || layer2.id === "workflow_deploy_gate") {
|
|
254156
254203
|
return layer2.reason_code === REASON.ACTIVE_JOB;
|
|
254157
254204
|
}
|
|
@@ -254603,15 +254650,48 @@ var require_github_enforcement = __commonJS({
|
|
|
254603
254650
|
environmentRead,
|
|
254604
254651
|
environmentName,
|
|
254605
254652
|
local,
|
|
254606
|
-
auth
|
|
254653
|
+
auth,
|
|
254654
|
+
rulesetsRead = null,
|
|
254655
|
+
branch = null
|
|
254607
254656
|
} = {}) {
|
|
254608
254657
|
const layers = [];
|
|
254609
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
|
+
};
|
|
254610
254684
|
const noAuth = !auth || auth.source === "none" || !auth.ok;
|
|
254611
254685
|
const prot = protectionRead || null;
|
|
254612
254686
|
if (noAuth && !prot) {
|
|
254613
254687
|
const why = auth && auth.reason || NO_TOKEN_INSTRUCTION;
|
|
254614
|
-
|
|
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"]) {
|
|
254615
254695
|
layers.push(layer(id, STATUS.UNVERIFIABLE, {
|
|
254616
254696
|
reason: why,
|
|
254617
254697
|
endpoint: null,
|
|
@@ -254620,13 +254700,21 @@ var require_github_enforcement = __commonJS({
|
|
|
254620
254700
|
}
|
|
254621
254701
|
} else if (!prot) {
|
|
254622
254702
|
const why = "branch protection was not queried";
|
|
254623
|
-
|
|
254703
|
+
pushRequiredCheck({ status: STATUS.UNVERIFIABLE, reason: why, endpoint: null });
|
|
254704
|
+
for (const id of ["enforce_admins", "strict_status_checks"]) {
|
|
254624
254705
|
layers.push(layer(id, STATUS.UNVERIFIABLE, { reason: why, endpoint: null }));
|
|
254625
254706
|
}
|
|
254626
254707
|
} else if (prot.status === 403) {
|
|
254627
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.";
|
|
254628
254709
|
const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
|
|
254629
|
-
|
|
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"]) {
|
|
254630
254718
|
layers.push(layer(id, STATUS.UNVERIFIABLE, {
|
|
254631
254719
|
reason: why,
|
|
254632
254720
|
endpoint,
|
|
@@ -254636,11 +254724,12 @@ var require_github_enforcement = __commonJS({
|
|
|
254636
254724
|
}
|
|
254637
254725
|
} else if (prot.status === 404) {
|
|
254638
254726
|
const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
|
|
254639
|
-
|
|
254727
|
+
pushRequiredCheck({
|
|
254728
|
+
status: STATUS.NOT_VERIFIED,
|
|
254640
254729
|
reason: `classic branch protection is absent \u2014 required check "${CHECK_NAME}" is not required`,
|
|
254641
254730
|
endpoint,
|
|
254642
254731
|
http_status: 404
|
|
254643
|
-
})
|
|
254732
|
+
});
|
|
254644
254733
|
layers.push(layer("enforce_admins", STATUS.NOT_VERIFIED, {
|
|
254645
254734
|
reason: "classic branch protection is absent \u2014 administrators are unrestricted",
|
|
254646
254735
|
endpoint,
|
|
@@ -254654,7 +254743,13 @@ var require_github_enforcement = __commonJS({
|
|
|
254654
254743
|
} else if (prot.status >= 400 || prot.status === 0) {
|
|
254655
254744
|
const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
|
|
254656
254745
|
const why = `GitHub HTTP ${prot.status || 0} reading branch protection \u2014 cannot verify`;
|
|
254657
|
-
|
|
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"]) {
|
|
254658
254753
|
layers.push(layer(id, STATUS.UNVERIFIABLE, {
|
|
254659
254754
|
reason: redactSecrets(why),
|
|
254660
254755
|
endpoint,
|
|
@@ -254665,14 +254760,15 @@ var require_github_enforcement = __commonJS({
|
|
|
254665
254760
|
const protection = prot.body && typeof prot.body === "object" ? prot.body : {};
|
|
254666
254761
|
const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
|
|
254667
254762
|
const required = evaluateRequiredCheck(protection);
|
|
254668
|
-
|
|
254763
|
+
pushRequiredCheck({
|
|
254764
|
+
status: required.status,
|
|
254669
254765
|
reason: required.reason,
|
|
254670
254766
|
reason_code: required.reason_code,
|
|
254671
254767
|
endpoint,
|
|
254672
254768
|
required_contexts: required.required_contexts,
|
|
254673
254769
|
app_id: required.app_id,
|
|
254674
254770
|
...required.expected_app_id != null ? { expected_app_id: required.expected_app_id } : {}
|
|
254675
|
-
})
|
|
254771
|
+
});
|
|
254676
254772
|
const admins = enforceAdminsEnabled(protection);
|
|
254677
254773
|
layers.push(layer("enforce_admins", admins ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
254678
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",
|
|
@@ -254840,11 +254936,27 @@ var require_github_enforcement = __commonJS({
|
|
|
254840
254936
|
const protPath = resolvedBranch ? `repos/${owner}/${repo}/branches/${encodeURIComponent(resolvedBranch)}/protection` : protectionPath;
|
|
254841
254937
|
const protectionRead = client && protPath ? await client.get(protPath) : null;
|
|
254842
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
|
+
}
|
|
254843
254954
|
return {
|
|
254844
254955
|
branch: resolvedBranch,
|
|
254845
254956
|
repoRead,
|
|
254846
254957
|
protectionRead,
|
|
254847
|
-
environmentRead
|
|
254958
|
+
environmentRead,
|
|
254959
|
+
rulesetsRead
|
|
254848
254960
|
};
|
|
254849
254961
|
}
|
|
254850
254962
|
module2.exports = {
|
|
@@ -255808,6 +255920,7 @@ var require_enforce_check = __commonJS({
|
|
|
255808
255920
|
let branch = options.branch ? String(options.branch).trim() : null;
|
|
255809
255921
|
let protectionRead = null;
|
|
255810
255922
|
let environmentRead = null;
|
|
255923
|
+
let rulesetsRead = null;
|
|
255811
255924
|
if (auth.ok) {
|
|
255812
255925
|
const client = deps.githubClient || createGitHubHttpClient({
|
|
255813
255926
|
token: auth.token,
|
|
@@ -255823,13 +255936,18 @@ var require_enforce_check = __commonJS({
|
|
|
255823
255936
|
branch = queried.branch || branch;
|
|
255824
255937
|
protectionRead = queried.protectionRead;
|
|
255825
255938
|
environmentRead = queried.environmentRead;
|
|
255939
|
+
rulesetsRead = queried.rulesetsRead;
|
|
255826
255940
|
}
|
|
255827
255941
|
const evaluated = evaluateLayers({
|
|
255828
255942
|
protectionRead,
|
|
255829
255943
|
environmentRead,
|
|
255830
255944
|
environmentName,
|
|
255831
255945
|
local,
|
|
255832
|
-
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
|
|
255833
255951
|
});
|
|
255834
255952
|
const report = {
|
|
255835
255953
|
command: "enforce --check",
|
|
@@ -255849,7 +255967,9 @@ var require_enforce_check = __commonJS({
|
|
|
255849
255967
|
// response or to our grading of it.
|
|
255850
255968
|
responses: {
|
|
255851
255969
|
protection: responseDigest(protectionRead),
|
|
255852
|
-
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 }
|
|
255853
255973
|
},
|
|
255854
255974
|
...evaluated
|
|
255855
255975
|
};
|
package/package.json
CHANGED