coderifts 4.6.0 → 4.8.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 +66 -0
- package/README.md +71 -5
- package/bin/coderifts.js +7 -2
- package/dist/cli.js +793 -76
- package/package.json +1 -1
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: "4.
|
|
3031
|
+
version: "4.8.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",
|
|
@@ -74363,7 +74363,7 @@ var require_claude_hook = __commonJS({
|
|
|
74363
74363
|
"Parse-gap DEFAULT (exit 2): unparseable stdin / missing file_path \u2014 refusing (fail-closed);",
|
|
74364
74364
|
" set CODERIFTS_ADVISORY=1 to soften. JSONL hook_blocked cause stdin_unparseable|missing_file_path.",
|
|
74365
74365
|
"",
|
|
74366
|
-
"Still exit 0 (nothing to govern): non-spec path, identical content.",
|
|
74366
|
+
"Still exit 0 (nothing to govern): non-spec path, identical content, non-governed tool.",
|
|
74367
74367
|
"",
|
|
74368
74368
|
"CONTINUE_WITH_MONITORING: allow only if the host asserts a sink",
|
|
74369
74369
|
" (CODERIFTS_MONITORING_SINK_WIRED=1|true or git config coderifts.monitoringSinkWired).",
|
|
@@ -74504,6 +74504,42 @@ var require_claude_hook = __commonJS({
|
|
|
74504
74504
|
if (fromGit) return fromGit;
|
|
74505
74505
|
return DEFAULT_SPEC_PATH;
|
|
74506
74506
|
}
|
|
74507
|
+
var GOVERNED_TOOLS = /* @__PURE__ */ new Set(["write", "edit", "multiedit", "multi_edit"]);
|
|
74508
|
+
function isGovernedTool(name) {
|
|
74509
|
+
return GOVERNED_TOOLS.has(String(name || "").toLowerCase());
|
|
74510
|
+
}
|
|
74511
|
+
function pickToolField(obj, ...keys) {
|
|
74512
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return void 0;
|
|
74513
|
+
for (const k of keys) {
|
|
74514
|
+
if (!Object.prototype.hasOwnProperty.call(obj, k)) continue;
|
|
74515
|
+
const v = obj[k];
|
|
74516
|
+
if (v === void 0 || v === null) continue;
|
|
74517
|
+
return v;
|
|
74518
|
+
}
|
|
74519
|
+
return void 0;
|
|
74520
|
+
}
|
|
74521
|
+
function filePathFromInput(toolInput) {
|
|
74522
|
+
const v = pickToolField(toolInput, "file_path", "filePath", "path", "target_file", "targetFile");
|
|
74523
|
+
return typeof v === "string" && v !== "" ? v : void 0;
|
|
74524
|
+
}
|
|
74525
|
+
function contentFromInput(toolInput) {
|
|
74526
|
+
const v = pickToolField(toolInput, "content", "contents");
|
|
74527
|
+
return typeof v === "string" ? v : void 0;
|
|
74528
|
+
}
|
|
74529
|
+
function coerceToolInput(value) {
|
|
74530
|
+
if (value && typeof value === "object" && !Array.isArray(value)) return value;
|
|
74531
|
+
if (typeof value === "string") {
|
|
74532
|
+
const t = value.trim();
|
|
74533
|
+
if (!t) return null;
|
|
74534
|
+
try {
|
|
74535
|
+
const parsed = JSON.parse(t);
|
|
74536
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
74537
|
+
} catch {
|
|
74538
|
+
return null;
|
|
74539
|
+
}
|
|
74540
|
+
}
|
|
74541
|
+
return null;
|
|
74542
|
+
}
|
|
74507
74543
|
function parseStdinJson(raw) {
|
|
74508
74544
|
if (raw == null || String(raw).trim() === "") {
|
|
74509
74545
|
return { ok: false, reason: "empty stdin" };
|
|
@@ -74517,9 +74553,9 @@ var require_claude_hook = __commonJS({
|
|
|
74517
74553
|
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
|
|
74518
74554
|
return { ok: false, reason: "stdin JSON is not an object" };
|
|
74519
74555
|
}
|
|
74520
|
-
const toolName = obj
|
|
74521
|
-
let toolInput = obj
|
|
74522
|
-
if (toolInput == null && obj
|
|
74556
|
+
const toolName = pickToolField(obj, "tool_name", "toolName", "name");
|
|
74557
|
+
let toolInput = coerceToolInput(pickToolField(obj, "tool_input", "toolInput", "input"));
|
|
74558
|
+
if (toolInput == null && filePathFromInput(obj)) toolInput = obj;
|
|
74523
74559
|
if (!toolName || typeof toolName !== "string") {
|
|
74524
74560
|
return { ok: false, reason: "missing tool_name" };
|
|
74525
74561
|
}
|
|
@@ -74543,16 +74579,17 @@ var require_claude_hook = __commonJS({
|
|
|
74543
74579
|
return false;
|
|
74544
74580
|
}
|
|
74545
74581
|
function deriveAfterContent(toolName, toolInput, diskBefore) {
|
|
74546
|
-
const name = String(toolName || "");
|
|
74547
|
-
if (name === "
|
|
74548
|
-
|
|
74582
|
+
const name = String(toolName || "").toLowerCase();
|
|
74583
|
+
if (name === "write") {
|
|
74584
|
+
const content = contentFromInput(toolInput);
|
|
74585
|
+
if (typeof content !== "string") {
|
|
74549
74586
|
return { ok: false, reason: "Write tool_input.content missing or not a string" };
|
|
74550
74587
|
}
|
|
74551
|
-
return { ok: true, after:
|
|
74588
|
+
return { ok: true, after: content };
|
|
74552
74589
|
}
|
|
74553
|
-
if (name === "
|
|
74554
|
-
const oldS = toolInput
|
|
74555
|
-
const newS = toolInput
|
|
74590
|
+
if (name === "edit") {
|
|
74591
|
+
const oldS = pickToolField(toolInput, "old_string", "oldString");
|
|
74592
|
+
const newS = pickToolField(toolInput, "new_string", "newString");
|
|
74556
74593
|
if (typeof oldS !== "string" || typeof newS !== "string") {
|
|
74557
74594
|
return { ok: false, reason: "Edit tool_input.old_string/new_string missing" };
|
|
74558
74595
|
}
|
|
@@ -74561,16 +74598,16 @@ var require_claude_hook = __commonJS({
|
|
|
74561
74598
|
}
|
|
74562
74599
|
return { ok: true, after: diskBefore.replace(oldS, newS) };
|
|
74563
74600
|
}
|
|
74564
|
-
if (name === "
|
|
74565
|
-
const edits = toolInput
|
|
74601
|
+
if (name === "multiedit" || name === "multi_edit") {
|
|
74602
|
+
const edits = pickToolField(toolInput, "edits", "Edits");
|
|
74566
74603
|
if (!Array.isArray(edits) || edits.length === 0) {
|
|
74567
74604
|
return { ok: false, reason: "MultiEdit tool_input.edits missing or empty" };
|
|
74568
74605
|
}
|
|
74569
74606
|
let cur = diskBefore;
|
|
74570
74607
|
for (let i = 0; i < edits.length; i++) {
|
|
74571
74608
|
const e = edits[i] || {};
|
|
74572
|
-
const oldS = e
|
|
74573
|
-
const newS = e
|
|
74609
|
+
const oldS = pickToolField(e, "old_string", "oldString");
|
|
74610
|
+
const newS = pickToolField(e, "new_string", "newString");
|
|
74574
74611
|
if (typeof oldS !== "string" || typeof newS !== "string") {
|
|
74575
74612
|
return { ok: false, reason: `MultiEdit edits[${i}] missing old_string/new_string` };
|
|
74576
74613
|
}
|
|
@@ -74581,7 +74618,7 @@ var require_claude_hook = __commonJS({
|
|
|
74581
74618
|
}
|
|
74582
74619
|
return { ok: true, after: cur };
|
|
74583
74620
|
}
|
|
74584
|
-
return { ok: false, reason: `unsupported tool_name for content derive: ${
|
|
74621
|
+
return { ok: false, reason: `unsupported tool_name for content derive: ${toolName}` };
|
|
74585
74622
|
}
|
|
74586
74623
|
function isV2DecisionBody(d, dr) {
|
|
74587
74624
|
if (dr && typeof dr === "object") return true;
|
|
@@ -74742,18 +74779,6 @@ var require_claude_hook = __commonJS({
|
|
|
74742
74779
|
}
|
|
74743
74780
|
return r;
|
|
74744
74781
|
};
|
|
74745
|
-
const apiKey = resolveApiKey({ ...deps, cwd, env });
|
|
74746
|
-
if (!apiKey) {
|
|
74747
|
-
logEv({ type: "preflight_unavailable", cause: "missing_api_key" });
|
|
74748
|
-
return emitTerminal(failClosedOrAdvisory({
|
|
74749
|
-
advisory,
|
|
74750
|
-
strict,
|
|
74751
|
-
site: "missing_api_key",
|
|
74752
|
-
why: "no API key",
|
|
74753
|
-
softMsg: "CodeRifts claude-hook: no API key (coderifts login / CODERIFTS_API_KEY / git config coderifts.apiKey) \u2014 allowing",
|
|
74754
|
-
errLog
|
|
74755
|
-
}));
|
|
74756
|
-
}
|
|
74757
74782
|
const PARSE_GAP_STDERR = "unparseable input \u2014 refusing (fail-closed); set CODERIFTS_ADVISORY=1 to soften";
|
|
74758
74783
|
const raw = typeof options.stdin === "string" ? options.stdin : readStdin();
|
|
74759
74784
|
const parsed = parseStdinJson(raw);
|
|
@@ -74767,7 +74792,11 @@ var require_claude_hook = __commonJS({
|
|
|
74767
74792
|
}
|
|
74768
74793
|
const { toolName, toolInput } = parsed;
|
|
74769
74794
|
tool = toolName;
|
|
74770
|
-
|
|
74795
|
+
if (!isGovernedTool(toolName)) {
|
|
74796
|
+
logEv({ type: "detection_skip", signals: ["not_governed_tool"], tool });
|
|
74797
|
+
return emitTerminal({ exitCode: 0, reason: "not_governed_tool" });
|
|
74798
|
+
}
|
|
74799
|
+
const filePath = filePathFromInput(toolInput);
|
|
74771
74800
|
if (!filePath || typeof filePath !== "string") {
|
|
74772
74801
|
if (advisory && !strict) {
|
|
74773
74802
|
errLog("CodeRifts claude-hook: tool_input.file_path missing \u2014 allowing (CODERIFTS_ADVISORY)");
|
|
@@ -74782,6 +74811,18 @@ var require_claude_hook = __commonJS({
|
|
|
74782
74811
|
logEv({ type: "detection_skip", signals: ["not_spec_path"], tool, path: filePath });
|
|
74783
74812
|
return emitTerminal({ exitCode: 0, reason: "not_spec_path" });
|
|
74784
74813
|
}
|
|
74814
|
+
const apiKey = resolveApiKey({ ...deps, cwd, env });
|
|
74815
|
+
if (!apiKey) {
|
|
74816
|
+
logEv({ type: "preflight_unavailable", cause: "missing_api_key" });
|
|
74817
|
+
return emitTerminal(failClosedOrAdvisory({
|
|
74818
|
+
advisory,
|
|
74819
|
+
strict,
|
|
74820
|
+
site: "missing_api_key",
|
|
74821
|
+
why: "no API key",
|
|
74822
|
+
softMsg: "CodeRifts claude-hook: no API key (coderifts login / CODERIFTS_API_KEY / git config coderifts.apiKey) \u2014 allowing",
|
|
74823
|
+
errLog
|
|
74824
|
+
}));
|
|
74825
|
+
}
|
|
74785
74826
|
const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
|
|
74786
74827
|
let diskBefore = "";
|
|
74787
74828
|
if (exists(absPath)) {
|
|
@@ -74941,6 +74982,11 @@ var require_claude_hook = __commonJS({
|
|
|
74941
74982
|
module2.exports = {
|
|
74942
74983
|
runClaudeHook,
|
|
74943
74984
|
parseStdinJson,
|
|
74985
|
+
pickToolField,
|
|
74986
|
+
filePathFromInput,
|
|
74987
|
+
contentFromInput,
|
|
74988
|
+
isGovernedTool,
|
|
74989
|
+
coerceToolInput,
|
|
74944
74990
|
isSpecPath,
|
|
74945
74991
|
deriveAfterContent,
|
|
74946
74992
|
mapDecisionSeverity,
|
|
@@ -142589,6 +142635,13 @@ var require_key_store = __commonJS({
|
|
|
142589
142635
|
function findByPrefix(prefix) {
|
|
142590
142636
|
return keyRecords.find((r) => r.keyPrefix === prefix) || null;
|
|
142591
142637
|
}
|
|
142638
|
+
function findAllByPrefix(prefix) {
|
|
142639
|
+
if (!prefix || typeof prefix !== "string") return [];
|
|
142640
|
+
return keyRecords.filter((r) => r.keyPrefix === prefix);
|
|
142641
|
+
}
|
|
142642
|
+
function isFullKeyToken(token) {
|
|
142643
|
+
return typeof token === "string" && token.length > 12;
|
|
142644
|
+
}
|
|
142592
142645
|
function findRecordByKey(key) {
|
|
142593
142646
|
if (!key || typeof key !== "string") return null;
|
|
142594
142647
|
const prefix = getKeyPrefix(key);
|
|
@@ -142672,14 +142725,38 @@ var require_key_store = __commonJS({
|
|
|
142672
142725
|
function _setGenerateKeyStringForTest(fn) {
|
|
142673
142726
|
_generateKeyStringForTest = typeof fn === "function" ? fn : null;
|
|
142674
142727
|
}
|
|
142675
|
-
function
|
|
142676
|
-
const idx = keyRecords.
|
|
142728
|
+
function deleteRecord(record, ownerEmail) {
|
|
142729
|
+
const idx = keyRecords.indexOf(record);
|
|
142677
142730
|
if (idx === -1) return false;
|
|
142678
142731
|
if (ownerEmail && keyRecords[idx].owner !== ownerEmail) return false;
|
|
142679
142732
|
keyRecords.splice(idx, 1);
|
|
142680
142733
|
saveKeys();
|
|
142681
142734
|
return true;
|
|
142682
142735
|
}
|
|
142736
|
+
function revokeKey({ token, ownerEmail } = {}) {
|
|
142737
|
+
const raw = typeof token === "string" ? token : "";
|
|
142738
|
+
if (!raw) return { ok: false, code: "not_found", count: 0 };
|
|
142739
|
+
if (isFullKeyToken(raw)) {
|
|
142740
|
+
const rec = findRecordByKey(raw);
|
|
142741
|
+
if (!rec) return { ok: false, code: "not_found", count: 0 };
|
|
142742
|
+
if (!deleteRecord(rec, ownerEmail || null)) {
|
|
142743
|
+
return { ok: false, code: "unauthorized", count: 1, prefix: rec.keyPrefix };
|
|
142744
|
+
}
|
|
142745
|
+
return { ok: true, code: "deleted", count: 1, prefix: rec.keyPrefix };
|
|
142746
|
+
}
|
|
142747
|
+
const matches = findAllByPrefix(raw);
|
|
142748
|
+
if (matches.length === 0) return { ok: false, code: "not_found", count: 0 };
|
|
142749
|
+
if (matches.length > 1) {
|
|
142750
|
+
return { ok: false, code: "ambiguous", count: matches.length, prefix: raw };
|
|
142751
|
+
}
|
|
142752
|
+
if (!deleteRecord(matches[0], ownerEmail || null)) {
|
|
142753
|
+
return { ok: false, code: "unauthorized", count: 1, prefix: raw };
|
|
142754
|
+
}
|
|
142755
|
+
return { ok: true, code: "deleted", count: 1, prefix: raw };
|
|
142756
|
+
}
|
|
142757
|
+
function deleteByPrefix(prefix, ownerEmail) {
|
|
142758
|
+
return revokeKey({ token: prefix, ownerEmail }).ok === true;
|
|
142759
|
+
}
|
|
142683
142760
|
module2.exports = {
|
|
142684
142761
|
createKey,
|
|
142685
142762
|
hasKey,
|
|
@@ -142689,7 +142766,9 @@ var require_key_store = __commonJS({
|
|
|
142689
142766
|
listKeysByOwner,
|
|
142690
142767
|
deleteByPrefix,
|
|
142691
142768
|
findByPrefix,
|
|
142769
|
+
findAllByPrefix,
|
|
142692
142770
|
findRecordByKey,
|
|
142771
|
+
revokeKey,
|
|
142693
142772
|
FREE_TIER_REQUESTS_PER_MONTH,
|
|
142694
142773
|
FREE_TIER_REQUESTS_PER_MINUTE,
|
|
142695
142774
|
PREFIX_REGENERATE_ATTEMPTS,
|
|
@@ -198815,9 +198894,10 @@ var require_remediation_taxonomy = __commonJS({
|
|
|
198815
198894
|
break;
|
|
198816
198895
|
}
|
|
198817
198896
|
case "COVERAGE_GAP": {
|
|
198818
|
-
|
|
198897
|
+
const detail = str(pattern && pattern.description) || str(pattern && pattern.error);
|
|
198898
|
+
target = str(pattern && pattern.affected_path) || "analysis";
|
|
198819
198899
|
target_ref = {};
|
|
198820
|
-
instruction = "Do not treat this as a schema edit: restore analyzable artifacts / wait for coverage; re-preflight when analysis is complete.";
|
|
198900
|
+
instruction = detail ? `fix the spec: ${detail}` : "Do not treat this as a schema edit: restore analyzable artifacts / wait for coverage; re-preflight when analysis is complete.";
|
|
198821
198901
|
break;
|
|
198822
198902
|
}
|
|
198823
198903
|
default: {
|
|
@@ -198872,6 +198952,21 @@ var require_remediation_taxonomy = __commonJS({
|
|
|
198872
198952
|
if (patterns.length > 0) {
|
|
198873
198953
|
return patterns.map((pattern) => remediationFromDetection({ pattern, ir: null }));
|
|
198874
198954
|
}
|
|
198955
|
+
const degradedReasons = Array.isArray(verdict.degraded_reasons) ? verdict.degraded_reasons : [];
|
|
198956
|
+
if (degradedReasons.length > 0) {
|
|
198957
|
+
return degradedReasons.map((r) => {
|
|
198958
|
+
const msg = r && (r.message || r.code) ? String(r.message || r.code) : "analysis incomplete";
|
|
198959
|
+
return remediationFromDetection({
|
|
198960
|
+
pattern: {
|
|
198961
|
+
name: "COVERAGE_GAP",
|
|
198962
|
+
degraded: true,
|
|
198963
|
+
description: msg,
|
|
198964
|
+
affected_path: r && r.code || "analysis"
|
|
198965
|
+
},
|
|
198966
|
+
ir: null
|
|
198967
|
+
});
|
|
198968
|
+
});
|
|
198969
|
+
}
|
|
198875
198970
|
if (verdict.coverage_gap === true || verdict.degraded === true) {
|
|
198876
198971
|
return [remediationFromDetection({ pattern: { name: "COVERAGE_GAP", degraded: true }, ir: null })];
|
|
198877
198972
|
}
|
|
@@ -206628,8 +206723,19 @@ var require_remediation_transaction = __commonJS({
|
|
|
206628
206723
|
if (artifactIds.length > 0) scope.artifact_ids = artifactIds;
|
|
206629
206724
|
return scope;
|
|
206630
206725
|
}
|
|
206726
|
+
function isDegradedContext(coreVerdict, context = {}) {
|
|
206727
|
+
if (context.analysis_complete === false) return true;
|
|
206728
|
+
if (Array.isArray(context.degraded_reasons) && context.degraded_reasons.length > 0) return true;
|
|
206729
|
+
if (coreVerdict && coreVerdict.degraded === true) return true;
|
|
206730
|
+
if (Array.isArray(coreVerdict && coreVerdict.degraded_reasons) && coreVerdict.degraded_reasons.length > 0) {
|
|
206731
|
+
return true;
|
|
206732
|
+
}
|
|
206733
|
+
return false;
|
|
206734
|
+
}
|
|
206631
206735
|
function buildRemediationTransaction({ decision, fingerprint, coreVerdict, context = {} }) {
|
|
206632
|
-
|
|
206736
|
+
const degraded = isDegradedContext(coreVerdict, context);
|
|
206737
|
+
const emit = decision === "BLOCK" || decision === "REQUIRE_APPROVAL" || degraded;
|
|
206738
|
+
if (!emit) return null;
|
|
206633
206739
|
if (typeof fingerprint !== "string" || !fingerprint) return null;
|
|
206634
206740
|
const verdictForTaxonomy = {
|
|
206635
206741
|
...coreVerdict && typeof coreVerdict === "object" ? coreVerdict : {}
|
|
@@ -206639,6 +206745,13 @@ var require_remediation_transaction = __commonJS({
|
|
|
206639
206745
|
verdictForTaxonomy.detected_patterns = context.detectedPatterns;
|
|
206640
206746
|
}
|
|
206641
206747
|
}
|
|
206748
|
+
if (degraded) {
|
|
206749
|
+
verdictForTaxonomy.degraded = true;
|
|
206750
|
+
const reasons = Array.isArray(context.degraded_reasons) && context.degraded_reasons.length ? context.degraded_reasons : verdictForTaxonomy.degraded_reasons;
|
|
206751
|
+
if (Array.isArray(reasons) && reasons.length) {
|
|
206752
|
+
verdictForTaxonomy.degraded_reasons = reasons;
|
|
206753
|
+
}
|
|
206754
|
+
}
|
|
206642
206755
|
const required_changes = buildRemediations(verdictForTaxonomy);
|
|
206643
206756
|
const profile = resolveFingerprintProfile(context);
|
|
206644
206757
|
return {
|
|
@@ -206673,6 +206786,7 @@ var require_remediation_transaction = __commonJS({
|
|
|
206673
206786
|
resolveFingerprintProfile,
|
|
206674
206787
|
deriveRecheckScope,
|
|
206675
206788
|
blockRequiredActionCore,
|
|
206789
|
+
isDegradedContext,
|
|
206676
206790
|
PROFILE_CRBUNDLE_V1,
|
|
206677
206791
|
PROFILE_VERDICT_FP_V1,
|
|
206678
206792
|
RESUBMISSION_UNCHANGED,
|
|
@@ -206848,7 +206962,7 @@ var require_decision_result = __commonJS({
|
|
|
206848
206962
|
pattern_sources: null,
|
|
206849
206963
|
// Control core for retrieval; null = not supplied (must never render as type none for BLOCK)
|
|
206850
206964
|
required_action_core: null,
|
|
206851
|
-
// ID850 v1 —
|
|
206965
|
+
// ID850 v1 — remediation transaction (null / omit on ALLOW/WARN with complete analysis)
|
|
206852
206966
|
remediation_transaction: null,
|
|
206853
206967
|
// ID637 v5 slice 4 — change-set completeness commitment (honest; not a proof on generic path).
|
|
206854
206968
|
// SERVER-authored mode; body_hash covered. Never boolean completeness:true.
|
|
@@ -207461,18 +207575,31 @@ var require_deploy_gate2 = __commonJS({
|
|
|
207461
207575
|
return isEnvFlag(e, "CODERIFTS_DEPLOY_ADVISORY") || isEnvFlag(e, "CODERIFTS_ADVISORY");
|
|
207462
207576
|
}
|
|
207463
207577
|
function observeCDEnforcement(options = {}) {
|
|
207578
|
+
const evidence = options.providerEvidence;
|
|
207579
|
+
const hostClaimNoBypass = String(process.env.CODERIFTS_DEPLOY_NO_BYPASS || "").toLowerCase() === "true";
|
|
207580
|
+
if (evidence && evidence.inescapable_deploy === true) {
|
|
207581
|
+
return {
|
|
207582
|
+
enforcement: "ENFORCING",
|
|
207583
|
+
bypass_possible: false,
|
|
207584
|
+
step_is_required: true,
|
|
207585
|
+
required_step_name: "CodeRifts / deploy-gate",
|
|
207586
|
+
attestation_source: "provider_query",
|
|
207587
|
+
host_claim_no_bypass: hostClaimNoBypass
|
|
207588
|
+
};
|
|
207589
|
+
}
|
|
207464
207590
|
const envVal = String(process.env.CODERIFTS_DEPLOY_ENFORCE || "").toLowerCase();
|
|
207465
207591
|
let enforcement;
|
|
207466
207592
|
if (enforceSignal(options)) enforcement = "ENFORCING";
|
|
207467
207593
|
else if (envVal === "unknown") enforcement = "UNKNOWN";
|
|
207468
207594
|
else enforcement = "ADVISORY";
|
|
207469
|
-
const bypass_possible = String(process.env.CODERIFTS_DEPLOY_NO_BYPASS || "").toLowerCase() !== "true";
|
|
207470
207595
|
return {
|
|
207471
207596
|
enforcement,
|
|
207472
|
-
bypass_possible,
|
|
207597
|
+
bypass_possible: true,
|
|
207598
|
+
// host NO_BYPASS is a claim, not queried evidence
|
|
207473
207599
|
step_is_required: enforcement === "ENFORCING",
|
|
207474
207600
|
required_step_name: "CodeRifts / deploy-gate",
|
|
207475
|
-
attestation_source: "cli_flag"
|
|
207601
|
+
attestation_source: "cli_flag",
|
|
207602
|
+
host_claim_no_bypass: hostClaimNoBypass
|
|
207476
207603
|
};
|
|
207477
207604
|
}
|
|
207478
207605
|
function deployReportResiduals(state, enforcement_inescapable, enforcement, change_set_rebound) {
|
|
@@ -207795,7 +207922,7 @@ var require_deploy_gate2 = __commonJS({
|
|
|
207795
207922
|
if (bind.report_residuals.length) lines.push(` Residuals: ${bind.report_residuals.join(", ")}`);
|
|
207796
207923
|
if (bind.must_re_preflight) lines.push(chalk.yellow(" Action: re-preflight this { environment, artifact } \u2014 the receipt does not authorize it."));
|
|
207797
207924
|
lines.push("");
|
|
207798
|
-
lines.push(enforce ? chalk.dim(" Attested ENFORCING (
|
|
207925
|
+
lines.push(enforce ? chalk.dim(" Attested ENFORCING via --enforce (host claim). inescapable_deploy requires queried pipeline protection (`coderifts enforce --check`). Exit is fail-closed by default.") : chalk.dim(" Fail-closed default \u2014 a failing gate exits non-zero. " + SOFTEN_HINT + "."));
|
|
207799
207926
|
lines.push("");
|
|
207800
207927
|
return lines.join("\n");
|
|
207801
207928
|
}
|
|
@@ -209791,6 +209918,7 @@ var require_mcp_repo_config = __commonJS({
|
|
|
209791
209918
|
id: "claude",
|
|
209792
209919
|
relPath: ".mcp.json",
|
|
209793
209920
|
label: "Claude Code",
|
|
209921
|
+
rootKey: "mcpServers",
|
|
209794
209922
|
// Measured: type+url required. url-without-type is a config error.
|
|
209795
209923
|
entry: Object.freeze({
|
|
209796
209924
|
type: "http",
|
|
@@ -209805,6 +209933,7 @@ var require_mcp_repo_config = __commonJS({
|
|
|
209805
209933
|
id: "cursor",
|
|
209806
209934
|
relPath: ".cursor/mcp.json",
|
|
209807
209935
|
label: "Cursor",
|
|
209936
|
+
rootKey: "mcpServers",
|
|
209808
209937
|
// Measured: url only — Cursor docs do not use type for remote HTTP.
|
|
209809
209938
|
entry: Object.freeze({
|
|
209810
209939
|
url: MCP_URL,
|
|
@@ -209813,8 +209942,24 @@ var require_mcp_repo_config = __commonJS({
|
|
|
209813
209942
|
})
|
|
209814
209943
|
}),
|
|
209815
209944
|
shapeNote: "mcpServers.coderifts url (no type \u2014 Cursor remote form)"
|
|
209945
|
+
}),
|
|
209946
|
+
copilot: Object.freeze({
|
|
209947
|
+
id: "copilot",
|
|
209948
|
+
relPath: ".vscode/mcp.json",
|
|
209949
|
+
label: "GitHub Copilot / VS Code",
|
|
209950
|
+
// Measured: THIRD variant — root key is `servers`, not mcpServers.
|
|
209951
|
+
rootKey: "servers",
|
|
209952
|
+
entry: Object.freeze({
|
|
209953
|
+
type: "http",
|
|
209954
|
+
url: MCP_URL,
|
|
209955
|
+
headers: Object.freeze({
|
|
209956
|
+
Authorization: "Bearer ${env:CODERIFTS_API_KEY}"
|
|
209957
|
+
})
|
|
209958
|
+
}),
|
|
209959
|
+
shapeNote: "servers.coderifts type=http url (root key servers, not mcpServers)"
|
|
209816
209960
|
})
|
|
209817
209961
|
});
|
|
209962
|
+
var MCP_HOST_IDS = Object.freeze(Object.keys(MCP_HOSTS));
|
|
209818
209963
|
function deepEqual(a, b) {
|
|
209819
209964
|
if (a === b) return true;
|
|
209820
209965
|
try {
|
|
@@ -209823,25 +209968,26 @@ var require_mcp_repo_config = __commonJS({
|
|
|
209823
209968
|
return false;
|
|
209824
209969
|
}
|
|
209825
209970
|
}
|
|
209826
|
-
function planMcpMerge(existing, entry) {
|
|
209971
|
+
function planMcpMerge(existing, entry, rootKey = "mcpServers") {
|
|
209972
|
+
const key = rootKey || "mcpServers";
|
|
209827
209973
|
const hadFile = existing != null;
|
|
209828
209974
|
const base = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
|
|
209829
|
-
const
|
|
209830
|
-
const current = Object.prototype.hasOwnProperty.call(
|
|
209975
|
+
const group = base[key] && typeof base[key] === "object" && !Array.isArray(base[key]) ? base[key] : {};
|
|
209976
|
+
const current = Object.prototype.hasOwnProperty.call(group, SERVER_NAME) ? group[SERVER_NAME] : void 0;
|
|
209831
209977
|
if (deepEqual(current, entry)) {
|
|
209832
209978
|
return { action: "skip", next: null, reason: "already present, identical" };
|
|
209833
209979
|
}
|
|
209834
209980
|
const next = {
|
|
209835
209981
|
...base,
|
|
209836
|
-
|
|
209837
|
-
...
|
|
209982
|
+
[key]: {
|
|
209983
|
+
...group,
|
|
209838
209984
|
[SERVER_NAME]: entry
|
|
209839
209985
|
}
|
|
209840
209986
|
};
|
|
209841
209987
|
if (!hadFile) {
|
|
209842
209988
|
return { action: "create", next, reason: "created" };
|
|
209843
209989
|
}
|
|
209844
|
-
const reason = current === void 0 ?
|
|
209990
|
+
const reason = current === void 0 ? `added ${key}.coderifts; preserved other servers` : `updated ${key}.coderifts; preserved other servers`;
|
|
209845
209991
|
return { action: "merge", next, reason };
|
|
209846
209992
|
}
|
|
209847
209993
|
function serializeMcp(doc) {
|
|
@@ -209910,7 +210056,7 @@ var require_mcp_repo_config = __commonJS({
|
|
|
209910
210056
|
existing = {};
|
|
209911
210057
|
}
|
|
209912
210058
|
}
|
|
209913
|
-
const plan = planMcpMerge(existing, host.entry);
|
|
210059
|
+
const plan = planMcpMerge(existing, host.entry, host.rootKey);
|
|
209914
210060
|
if (plan.action === "skip") {
|
|
209915
210061
|
return { action: "skip", relPath: host.relPath, reason: plan.reason, exitCode: 0 };
|
|
209916
210062
|
}
|
|
@@ -209947,7 +210093,7 @@ var require_mcp_repo_config = __commonJS({
|
|
|
209947
210093
|
if (!exists(fp)) return false;
|
|
209948
210094
|
try {
|
|
209949
210095
|
const doc = JSON.parse(readFile(fp));
|
|
209950
|
-
return planMcpMerge(doc, host.entry).action === "skip";
|
|
210096
|
+
return planMcpMerge(doc, host.entry, host.rootKey).action === "skip";
|
|
209951
210097
|
} catch {
|
|
209952
210098
|
return false;
|
|
209953
210099
|
}
|
|
@@ -209956,6 +210102,7 @@ var require_mcp_repo_config = __commonJS({
|
|
|
209956
210102
|
MCP_URL,
|
|
209957
210103
|
SERVER_NAME,
|
|
209958
210104
|
MCP_HOSTS,
|
|
210105
|
+
MCP_HOST_IDS,
|
|
209959
210106
|
planMcpMerge,
|
|
209960
210107
|
serializeMcp,
|
|
209961
210108
|
writeHostMcpConfig,
|
|
@@ -210071,6 +210218,7 @@ var require_init_agents = __commonJS({
|
|
|
210071
210218
|
var { installClaudeHook, installCursorHook } = require_hook();
|
|
210072
210219
|
var {
|
|
210073
210220
|
MCP_HOSTS,
|
|
210221
|
+
MCP_HOST_IDS,
|
|
210074
210222
|
writeHostMcpConfig,
|
|
210075
210223
|
mcpConfigPresent
|
|
210076
210224
|
} = require_mcp_repo_config();
|
|
@@ -210082,15 +210230,15 @@ var require_init_agents = __commonJS({
|
|
|
210082
210230
|
workflowPresent
|
|
210083
210231
|
} = require_contract_gate_workflow();
|
|
210084
210232
|
if (process.env.NO_COLOR) chalk.level = 0;
|
|
210085
|
-
var USAGE = `Usage: coderifts init --agents [--hosts=claude,cursor,all] [--no-hook] [--no-workflow] [--dry-run] [--check] [--out <dir>]
|
|
210233
|
+
var USAGE = `Usage: coderifts init --agents [--hosts=claude,cursor,copilot,all] [--no-hook] [--no-workflow] [--dry-run] [--check] [--out <dir>]
|
|
210086
210234
|
|
|
210087
210235
|
Wire this repository for governed agent work in ONE command:
|
|
210088
|
-
1. repo-level MCP config (per host: .mcp.json / .cursor/mcp.json)
|
|
210236
|
+
1. repo-level MCP config (per host: .mcp.json / .cursor/mcp.json / .vscode/mcp.json)
|
|
210089
210237
|
2. generated agent rule files (reuses agent-setup)
|
|
210090
|
-
3. host hook (Claude Code
|
|
210238
|
+
3. host hook (Claude Code / VS Code Copilot via .claude/settings.json; Cursor via .cursor/hooks.json)
|
|
210091
210239
|
4. .github/workflows/coderifts.yml (coderifts/contract-gate@v0)
|
|
210092
210240
|
|
|
210093
|
-
--hosts <list> claude, cursor, or all (default: all)
|
|
210241
|
+
--hosts <list> claude, cursor, copilot, or all (default: all)
|
|
210094
210242
|
--no-hook Skip host hook install
|
|
210095
210243
|
--no-workflow Skip writing the CI workflow
|
|
210096
210244
|
--dry-run Print the plan without writing
|
|
@@ -210101,7 +210249,8 @@ var require_init_agents = __commonJS({
|
|
|
210101
210249
|
var SHARED_RULE = "AGENTS.md";
|
|
210102
210250
|
var HOST_RULES = Object.freeze({
|
|
210103
210251
|
claude: Object.freeze(["CLAUDE.md"]),
|
|
210104
|
-
cursor: Object.freeze([".cursor/rules/coderifts.mdc"])
|
|
210252
|
+
cursor: Object.freeze([".cursor/rules/coderifts.mdc"]),
|
|
210253
|
+
copilot: Object.freeze([".github/copilot-instructions.md"])
|
|
210105
210254
|
});
|
|
210106
210255
|
var EXTRA_RULES_ON_ALL = Object.freeze([
|
|
210107
210256
|
".github/copilot-instructions.md",
|
|
@@ -210121,27 +210270,28 @@ var require_init_agents = __commonJS({
|
|
|
210121
210270
|
" \xB7 write Gemini MCP config (Gemini uses httpUrl at extension level, not a repo file)"
|
|
210122
210271
|
]);
|
|
210123
210272
|
function parseHosts(raw) {
|
|
210273
|
+
const allHosts = [...MCP_HOST_IDS];
|
|
210124
210274
|
if (raw == null || raw === true || String(raw).trim() === "") {
|
|
210125
|
-
return { hosts:
|
|
210275
|
+
return { hosts: allHosts, allRules: true };
|
|
210126
210276
|
}
|
|
210127
210277
|
const parts = String(raw).split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
210128
210278
|
if (parts.length === 0) {
|
|
210129
|
-
return { hosts:
|
|
210279
|
+
return { hosts: allHosts, allRules: true, error: `init --agents: --hosts requires a value
|
|
210130
210280
|
${USAGE}` };
|
|
210131
210281
|
}
|
|
210132
|
-
const allowed = /* @__PURE__ */ new Set([
|
|
210282
|
+
const allowed = /* @__PURE__ */ new Set([...MCP_HOST_IDS, "all"]);
|
|
210133
210283
|
for (const p of parts) {
|
|
210134
210284
|
if (!allowed.has(p)) {
|
|
210135
210285
|
return {
|
|
210136
210286
|
hosts: [],
|
|
210137
210287
|
allRules: false,
|
|
210138
|
-
error: `init --agents: unknown host "${p}" (use claude, cursor, all)
|
|
210288
|
+
error: `init --agents: unknown host "${p}" (use claude, cursor, copilot, all)
|
|
210139
210289
|
${USAGE}`
|
|
210140
210290
|
};
|
|
210141
210291
|
}
|
|
210142
210292
|
}
|
|
210143
210293
|
if (parts.includes("all")) {
|
|
210144
|
-
return { hosts:
|
|
210294
|
+
return { hosts: allHosts, allRules: true };
|
|
210145
210295
|
}
|
|
210146
210296
|
const hosts = [];
|
|
210147
210297
|
for (const p of parts) {
|
|
@@ -210173,7 +210323,25 @@ ${USAGE}`
|
|
|
210173
210323
|
return hostId === "cursor" ? ".cursor/hooks.json" : ".claude/settings.json";
|
|
210174
210324
|
}
|
|
210175
210325
|
function hookNoteForHost(hostId) {
|
|
210176
|
-
|
|
210326
|
+
if (hostId === "cursor") return "preToolUse \u2192 coderifts cursor-hook";
|
|
210327
|
+
if (hostId === "copilot") {
|
|
210328
|
+
return "PreToolUse \u2192 coderifts claude-hook (VS Code reads .claude/settings.json by default)";
|
|
210329
|
+
}
|
|
210330
|
+
return "PreToolUse \u2192 coderifts claude-hook";
|
|
210331
|
+
}
|
|
210332
|
+
function hookInstallerForHost(hostId) {
|
|
210333
|
+
return hostId === "cursor" ? installCursorHook : installClaudeHook;
|
|
210334
|
+
}
|
|
210335
|
+
function uniqueHookHosts(hosts) {
|
|
210336
|
+
const seen = /* @__PURE__ */ new Set();
|
|
210337
|
+
const out = [];
|
|
210338
|
+
for (const h of hosts) {
|
|
210339
|
+
const rel = hookRelForHost(h);
|
|
210340
|
+
if (seen.has(rel)) continue;
|
|
210341
|
+
seen.add(rel);
|
|
210342
|
+
out.push(h);
|
|
210343
|
+
}
|
|
210344
|
+
return out;
|
|
210177
210345
|
}
|
|
210178
210346
|
function wantHook(options) {
|
|
210179
210347
|
if (options.noHook === true) return false;
|
|
@@ -210220,9 +210388,9 @@ ${USAGE}`
|
|
|
210220
210388
|
const hooksMissing = [];
|
|
210221
210389
|
const hooksWanted = wantHook(options);
|
|
210222
210390
|
if (hooksWanted) {
|
|
210223
|
-
for (const h of hosts) {
|
|
210391
|
+
for (const h of uniqueHookHosts(hosts)) {
|
|
210224
210392
|
const rel = hookRelForHost(h);
|
|
210225
|
-
const installer = h
|
|
210393
|
+
const installer = hookInstallerForHost(h);
|
|
210226
210394
|
const r = installer({ cwd: outDir, silent: true, dryRun: true });
|
|
210227
210395
|
if (r && r.code === "NOOP") hooksPresent.push(rel);
|
|
210228
210396
|
else hooksMissing.push(rel);
|
|
@@ -210353,7 +210521,7 @@ ${USAGE}`
|
|
|
210353
210521
|
items.push({ section: "rules", action: "skip", relPath: rel, note: "exists; installer does not overwrite" });
|
|
210354
210522
|
}
|
|
210355
210523
|
if (!wantHook(options)) {
|
|
210356
|
-
for (const h of hosts) {
|
|
210524
|
+
for (const h of uniqueHookHosts(hosts)) {
|
|
210357
210525
|
items.push({
|
|
210358
210526
|
section: "hooks",
|
|
210359
210527
|
action: "skip",
|
|
@@ -210362,8 +210530,8 @@ ${USAGE}`
|
|
|
210362
210530
|
});
|
|
210363
210531
|
}
|
|
210364
210532
|
} else {
|
|
210365
|
-
for (const h of hosts) {
|
|
210366
|
-
const installer = h
|
|
210533
|
+
for (const h of uniqueHookHosts(hosts)) {
|
|
210534
|
+
const installer = hookInstallerForHost(h);
|
|
210367
210535
|
const r = installer({
|
|
210368
210536
|
cwd: outDir,
|
|
210369
210537
|
silent: true,
|
|
@@ -236999,6 +237167,538 @@ var require_status = __commonJS({
|
|
|
236999
237167
|
}
|
|
237000
237168
|
});
|
|
237001
237169
|
|
|
237170
|
+
// src/provider/github-enforcement.js
|
|
237171
|
+
var require_github_enforcement = __commonJS({
|
|
237172
|
+
"src/provider/github-enforcement.js"(exports2, module2) {
|
|
237173
|
+
"use strict";
|
|
237174
|
+
var fs = require("fs");
|
|
237175
|
+
var path = require("path");
|
|
237176
|
+
var {
|
|
237177
|
+
CHECK_NAME,
|
|
237178
|
+
extractRequiredContexts
|
|
237179
|
+
} = require_setup_required_check();
|
|
237180
|
+
var { GATE_ACTION } = require_contract_gate_workflow();
|
|
237181
|
+
var EVIDENCE_SPEC = "provider-enforcement-evidence.v1";
|
|
237182
|
+
var APP_INSTALLATION = Object.freeze({
|
|
237183
|
+
can_query_protection: false,
|
|
237184
|
+
permissions: Object.freeze([
|
|
237185
|
+
"contents:read",
|
|
237186
|
+
"pull_requests:read+write",
|
|
237187
|
+
"checks:read+write"
|
|
237188
|
+
]),
|
|
237189
|
+
missing_scope: "administration:read",
|
|
237190
|
+
reason: "GET /repos/{owner}/{repo}/branches/{branch}/protection requires administration:read (or repo admin). The CodeRifts GitHub App permission set is contents:read, pull_requests:read+write, checks:read+write \u2014 administration:read is not granted."
|
|
237191
|
+
});
|
|
237192
|
+
var NO_TOKEN_INSTRUCTION = "NO token \u2014 set GITHUB_TOKEN (repo + administration:read; Environments: Read if querying --env). The CodeRifts GitHub App installation cannot query branch protection (permissions: contents:read, pull_requests:read+write, checks:read+write \u2014 missing administration:read).";
|
|
237193
|
+
var LAYER_IDS = Object.freeze([
|
|
237194
|
+
"required_check",
|
|
237195
|
+
"enforce_admins",
|
|
237196
|
+
"strict_status_checks",
|
|
237197
|
+
"environment_protection",
|
|
237198
|
+
"workflow_contract_gate",
|
|
237199
|
+
"workflow_deploy_gate"
|
|
237200
|
+
]);
|
|
237201
|
+
var STATUS = Object.freeze({
|
|
237202
|
+
VERIFIED: "VERIFIED",
|
|
237203
|
+
NOT_VERIFIED: "NOT_VERIFIED",
|
|
237204
|
+
UNVERIFIABLE: "UNVERIFIABLE"
|
|
237205
|
+
});
|
|
237206
|
+
function resolveGitHubAuth(env = process.env) {
|
|
237207
|
+
const user = String(env && (env.GITHUB_TOKEN || env.GH_TOKEN) || "").trim();
|
|
237208
|
+
if (user) {
|
|
237209
|
+
return { ok: true, source: "github_token", token: user };
|
|
237210
|
+
}
|
|
237211
|
+
const app = String(env && (env.GITHUB_APP_TOKEN || env.CODERIFTS_GITHUB_APP_TOKEN) || "").trim();
|
|
237212
|
+
if (app) {
|
|
237213
|
+
return { ok: true, source: "github_app_installation", token: app };
|
|
237214
|
+
}
|
|
237215
|
+
return { ok: false, source: "none", token: null, reason: NO_TOKEN_INSTRUCTION };
|
|
237216
|
+
}
|
|
237217
|
+
function redactSecrets(text) {
|
|
237218
|
+
return String(text || "").replace(/Bearer\s+\S+/gi, "Bearer [redacted]").replace(/ghp_[A-Za-z0-9_]+/g, "ghp_[redacted]").replace(/github_pat_[A-Za-z0-9_]+/g, "github_pat_[redacted]");
|
|
237219
|
+
}
|
|
237220
|
+
function createGitHubHttpClient(opts = {}) {
|
|
237221
|
+
const token = opts.token;
|
|
237222
|
+
const fetchFn = opts.fetchImpl || (typeof fetch === "function" ? fetch.bind(globalThis) : null);
|
|
237223
|
+
const apiBase = (opts.apiBase || "https://api.github.com").replace(/\/$/, "");
|
|
237224
|
+
return {
|
|
237225
|
+
name: "github",
|
|
237226
|
+
async get(apiPath) {
|
|
237227
|
+
const endpoint = `${apiBase}/${String(apiPath).replace(/^\//, "")}`;
|
|
237228
|
+
if (!fetchFn) {
|
|
237229
|
+
return {
|
|
237230
|
+
status: 0,
|
|
237231
|
+
body: null,
|
|
237232
|
+
endpoint,
|
|
237233
|
+
error: "fetch is not available in this runtime"
|
|
237234
|
+
};
|
|
237235
|
+
}
|
|
237236
|
+
const res = await fetchFn(endpoint, {
|
|
237237
|
+
method: "GET",
|
|
237238
|
+
headers: {
|
|
237239
|
+
Authorization: `Bearer ${token}`,
|
|
237240
|
+
Accept: "application/vnd.github+json",
|
|
237241
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
237242
|
+
"User-Agent": "coderifts-cli"
|
|
237243
|
+
}
|
|
237244
|
+
});
|
|
237245
|
+
const text = await res.text();
|
|
237246
|
+
let body = null;
|
|
237247
|
+
try {
|
|
237248
|
+
body = text ? JSON.parse(text) : null;
|
|
237249
|
+
} catch {
|
|
237250
|
+
body = null;
|
|
237251
|
+
}
|
|
237252
|
+
return { status: res.status, body, endpoint };
|
|
237253
|
+
}
|
|
237254
|
+
};
|
|
237255
|
+
}
|
|
237256
|
+
function enforceAdminsEnabled(protection) {
|
|
237257
|
+
if (!protection || typeof protection !== "object") return false;
|
|
237258
|
+
const ea = protection.enforce_admins;
|
|
237259
|
+
if (ea === true) return true;
|
|
237260
|
+
if (ea && typeof ea === "object" && ea.enabled === true) return true;
|
|
237261
|
+
return false;
|
|
237262
|
+
}
|
|
237263
|
+
function strictEnabled(protection) {
|
|
237264
|
+
if (!protection || typeof protection !== "object") return false;
|
|
237265
|
+
const rsc = protection.required_status_checks;
|
|
237266
|
+
return !!(rsc && typeof rsc === "object" && rsc.strict === true);
|
|
237267
|
+
}
|
|
237268
|
+
function environmentProtected(body) {
|
|
237269
|
+
if (!body || typeof body !== "object") return false;
|
|
237270
|
+
const rules = Array.isArray(body.protection_rules) ? body.protection_rules : [];
|
|
237271
|
+
if (rules.length > 0) return true;
|
|
237272
|
+
const policy = body.deployment_branch_policy;
|
|
237273
|
+
if (policy && typeof policy === "object") {
|
|
237274
|
+
return policy.protected_branches === true || policy.custom_branch_policies === true;
|
|
237275
|
+
}
|
|
237276
|
+
return false;
|
|
237277
|
+
}
|
|
237278
|
+
function layer(id, status, extra = {}) {
|
|
237279
|
+
return { id, status, ...extra };
|
|
237280
|
+
}
|
|
237281
|
+
function evaluateLayers({
|
|
237282
|
+
protectionRead,
|
|
237283
|
+
environmentRead,
|
|
237284
|
+
environmentName,
|
|
237285
|
+
local,
|
|
237286
|
+
auth
|
|
237287
|
+
} = {}) {
|
|
237288
|
+
const layers = [];
|
|
237289
|
+
const localFiles = local || { contractGateFiles: [], deployGateFiles: [] };
|
|
237290
|
+
const noAuth = !auth || auth.source === "none" || !auth.ok;
|
|
237291
|
+
const prot = protectionRead || null;
|
|
237292
|
+
if (noAuth && !prot) {
|
|
237293
|
+
const why = auth && auth.reason || NO_TOKEN_INSTRUCTION;
|
|
237294
|
+
for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
|
|
237295
|
+
layers.push(layer(id, STATUS.UNVERIFIABLE, {
|
|
237296
|
+
reason: why,
|
|
237297
|
+
endpoint: null,
|
|
237298
|
+
missing_scope: APP_INSTALLATION.missing_scope
|
|
237299
|
+
}));
|
|
237300
|
+
}
|
|
237301
|
+
} else if (!prot) {
|
|
237302
|
+
const why = "branch protection was not queried";
|
|
237303
|
+
for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
|
|
237304
|
+
layers.push(layer(id, STATUS.UNVERIFIABLE, { reason: why, endpoint: null }));
|
|
237305
|
+
}
|
|
237306
|
+
} else if (prot.status === 403) {
|
|
237307
|
+
const why = "GitHub HTTP 403 \u2014 missing scope administration:read (branch protection). Grant Administration: Read on the token, or use a repo-admin token.";
|
|
237308
|
+
const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
|
|
237309
|
+
for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
|
|
237310
|
+
layers.push(layer(id, STATUS.UNVERIFIABLE, {
|
|
237311
|
+
reason: why,
|
|
237312
|
+
endpoint,
|
|
237313
|
+
missing_scope: "administration:read",
|
|
237314
|
+
http_status: 403
|
|
237315
|
+
}));
|
|
237316
|
+
}
|
|
237317
|
+
} else if (prot.status === 404) {
|
|
237318
|
+
const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
|
|
237319
|
+
layers.push(layer("required_check", STATUS.NOT_VERIFIED, {
|
|
237320
|
+
reason: `classic branch protection is absent \u2014 required check "${CHECK_NAME}" is not required`,
|
|
237321
|
+
endpoint,
|
|
237322
|
+
http_status: 404
|
|
237323
|
+
}));
|
|
237324
|
+
layers.push(layer("enforce_admins", STATUS.NOT_VERIFIED, {
|
|
237325
|
+
reason: "classic branch protection is absent \u2014 administrators are unrestricted",
|
|
237326
|
+
endpoint,
|
|
237327
|
+
http_status: 404
|
|
237328
|
+
}));
|
|
237329
|
+
layers.push(layer("strict_status_checks", STATUS.NOT_VERIFIED, {
|
|
237330
|
+
reason: "classic branch protection is absent \u2014 required_status_checks.strict is not set",
|
|
237331
|
+
endpoint,
|
|
237332
|
+
http_status: 404
|
|
237333
|
+
}));
|
|
237334
|
+
} else if (prot.status >= 400 || prot.status === 0) {
|
|
237335
|
+
const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
|
|
237336
|
+
const why = `GitHub HTTP ${prot.status || 0} reading branch protection \u2014 cannot verify`;
|
|
237337
|
+
for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
|
|
237338
|
+
layers.push(layer(id, STATUS.UNVERIFIABLE, {
|
|
237339
|
+
reason: redactSecrets(why),
|
|
237340
|
+
endpoint,
|
|
237341
|
+
http_status: prot.status
|
|
237342
|
+
}));
|
|
237343
|
+
}
|
|
237344
|
+
} else {
|
|
237345
|
+
const protection = prot.body && typeof prot.body === "object" ? prot.body : {};
|
|
237346
|
+
const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
|
|
237347
|
+
const contexts = extractRequiredContexts(protection);
|
|
237348
|
+
const hasCheck = contexts.includes(CHECK_NAME);
|
|
237349
|
+
layers.push(layer("required_check", hasCheck ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
237350
|
+
reason: hasCheck ? `required_status_checks includes "${CHECK_NAME}"` : `required_status_checks does not include "${CHECK_NAME}" (have: ${contexts.join(", ") || "none"})`,
|
|
237351
|
+
endpoint,
|
|
237352
|
+
required_contexts: contexts
|
|
237353
|
+
}));
|
|
237354
|
+
const admins = enforceAdminsEnabled(protection);
|
|
237355
|
+
layers.push(layer("enforce_admins", admins ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
237356
|
+
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",
|
|
237357
|
+
endpoint,
|
|
237358
|
+
enforce_admins: admins
|
|
237359
|
+
}));
|
|
237360
|
+
const strict = strictEnabled(protection);
|
|
237361
|
+
layers.push(layer("strict_status_checks", strict ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
237362
|
+
reason: strict ? "required_status_checks.strict is true" : "required_status_checks.strict is not true \u2014 a branch need not be up to date before merge",
|
|
237363
|
+
endpoint,
|
|
237364
|
+
strict
|
|
237365
|
+
}));
|
|
237366
|
+
}
|
|
237367
|
+
if (!environmentName) {
|
|
237368
|
+
layers.push(layer("environment_protection", STATUS.UNVERIFIABLE, {
|
|
237369
|
+
reason: "no --env supplied; pass --env <GitHub Environment name> to query environment protection rules",
|
|
237370
|
+
endpoint: null
|
|
237371
|
+
}));
|
|
237372
|
+
} else if (noAuth && !environmentRead) {
|
|
237373
|
+
layers.push(layer("environment_protection", STATUS.UNVERIFIABLE, {
|
|
237374
|
+
reason: auth && auth.reason || NO_TOKEN_INSTRUCTION,
|
|
237375
|
+
endpoint: null,
|
|
237376
|
+
missing_scope: "actions:read / Environments: Read"
|
|
237377
|
+
}));
|
|
237378
|
+
} else if (!environmentRead) {
|
|
237379
|
+
layers.push(layer("environment_protection", STATUS.UNVERIFIABLE, {
|
|
237380
|
+
reason: "environment was not queried",
|
|
237381
|
+
endpoint: null
|
|
237382
|
+
}));
|
|
237383
|
+
} else if (environmentRead.status === 403) {
|
|
237384
|
+
layers.push(layer("environment_protection", STATUS.UNVERIFIABLE, {
|
|
237385
|
+
reason: "GitHub HTTP 403 \u2014 missing scope to read environments (Actions/Environments: Read, or repo admin).",
|
|
237386
|
+
endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
|
|
237387
|
+
missing_scope: "environments:read",
|
|
237388
|
+
http_status: 403
|
|
237389
|
+
}));
|
|
237390
|
+
} else if (environmentRead.status === 404) {
|
|
237391
|
+
layers.push(layer("environment_protection", STATUS.NOT_VERIFIED, {
|
|
237392
|
+
reason: `GitHub Environment "${environmentName}" does not exist (or has no protection)`,
|
|
237393
|
+
endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
|
|
237394
|
+
http_status: 404
|
|
237395
|
+
}));
|
|
237396
|
+
} else if (environmentRead.status >= 400 || environmentRead.status === 0) {
|
|
237397
|
+
layers.push(layer("environment_protection", STATUS.UNVERIFIABLE, {
|
|
237398
|
+
reason: `GitHub HTTP ${environmentRead.status || 0} reading environment "${environmentName}"`,
|
|
237399
|
+
endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
|
|
237400
|
+
http_status: environmentRead.status
|
|
237401
|
+
}));
|
|
237402
|
+
} else {
|
|
237403
|
+
const ok = environmentProtected(environmentRead.body);
|
|
237404
|
+
layers.push(layer("environment_protection", ok ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
237405
|
+
reason: ok ? `GitHub Environment "${environmentName}" has protection rules or a deployment branch policy` : `GitHub Environment "${environmentName}" exists but has no protection_rules and no deployment_branch_policy`,
|
|
237406
|
+
endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
|
|
237407
|
+
environment: environmentName
|
|
237408
|
+
}));
|
|
237409
|
+
}
|
|
237410
|
+
const cg = localFiles.contractGateFiles || [];
|
|
237411
|
+
layers.push(layer("workflow_contract_gate", cg.length ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
237412
|
+
reason: cg.length ? `workflow references ${GATE_ACTION} (${cg.join(", ")})` : `no .github/workflows file references ${GATE_ACTION}`,
|
|
237413
|
+
endpoint: "local:.github/workflows",
|
|
237414
|
+
files: cg
|
|
237415
|
+
}));
|
|
237416
|
+
const dg = localFiles.deployGateFiles || [];
|
|
237417
|
+
layers.push(layer("workflow_deploy_gate", dg.length ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
237418
|
+
reason: dg.length ? `workflow runs coderifts deploy-gate (${dg.join(", ")})` : "no .github/workflows file runs coderifts deploy-gate",
|
|
237419
|
+
endpoint: "local:.github/workflows",
|
|
237420
|
+
files: dg
|
|
237421
|
+
}));
|
|
237422
|
+
const byId = Object.fromEntries(layers.map((l) => [l.id, l]));
|
|
237423
|
+
const inescapable = LAYER_IDS.every((id) => byId[id] && byId[id].status === STATUS.VERIFIED);
|
|
237424
|
+
return {
|
|
237425
|
+
layers,
|
|
237426
|
+
inescapable_deploy: inescapable,
|
|
237427
|
+
claim: {
|
|
237428
|
+
inescapable_deploy: inescapable,
|
|
237429
|
+
basis: inescapable ? "all six layers VERIFIED from queried provider evidence + local workflow files" : "inescapable_deploy stays false until every layer is VERIFIED"
|
|
237430
|
+
}
|
|
237431
|
+
};
|
|
237432
|
+
}
|
|
237433
|
+
function scanLocalWorkflows(cwd, deps = {}) {
|
|
237434
|
+
const exists = deps.exists || fs.existsSync.bind(fs);
|
|
237435
|
+
const readdir = deps.readdir || ((p) => fs.readdirSync(p));
|
|
237436
|
+
const readFile = deps.readFile || ((p) => fs.readFileSync(p, "utf8"));
|
|
237437
|
+
const dir = path.join(cwd, ".github", "workflows");
|
|
237438
|
+
const contractGateFiles = [];
|
|
237439
|
+
const deployGateFiles = [];
|
|
237440
|
+
if (!exists(dir)) return { contractGateFiles, deployGateFiles };
|
|
237441
|
+
let names = [];
|
|
237442
|
+
try {
|
|
237443
|
+
names = readdir(dir);
|
|
237444
|
+
} catch {
|
|
237445
|
+
return { contractGateFiles, deployGateFiles };
|
|
237446
|
+
}
|
|
237447
|
+
for (const name of names) {
|
|
237448
|
+
if (!/\.ya?ml$/i.test(name)) continue;
|
|
237449
|
+
const rel = `.github/workflows/${name}`;
|
|
237450
|
+
let body = "";
|
|
237451
|
+
try {
|
|
237452
|
+
body = String(readFile(path.join(dir, name)));
|
|
237453
|
+
} catch {
|
|
237454
|
+
continue;
|
|
237455
|
+
}
|
|
237456
|
+
if (body.includes(GATE_ACTION)) contractGateFiles.push(rel);
|
|
237457
|
+
if (/\bcoderifts\s+deploy-gate\b/.test(body)) deployGateFiles.push(rel);
|
|
237458
|
+
}
|
|
237459
|
+
return { contractGateFiles, deployGateFiles };
|
|
237460
|
+
}
|
|
237461
|
+
async function queryGitHubEnforcement({
|
|
237462
|
+
owner,
|
|
237463
|
+
repo,
|
|
237464
|
+
branch,
|
|
237465
|
+
environmentName,
|
|
237466
|
+
client
|
|
237467
|
+
} = {}) {
|
|
237468
|
+
const protectionPath = branch ? `repos/${owner}/${repo}/branches/${encodeURIComponent(branch)}/protection` : null;
|
|
237469
|
+
const envPath = environmentName ? `repos/${owner}/${repo}/environments/${encodeURIComponent(environmentName)}` : null;
|
|
237470
|
+
let resolvedBranch = branch || null;
|
|
237471
|
+
let repoRead = null;
|
|
237472
|
+
if (!resolvedBranch && client) {
|
|
237473
|
+
repoRead = await client.get(`repos/${owner}/${repo}`);
|
|
237474
|
+
if (repoRead && repoRead.status === 200 && repoRead.body && repoRead.body.default_branch) {
|
|
237475
|
+
resolvedBranch = String(repoRead.body.default_branch);
|
|
237476
|
+
}
|
|
237477
|
+
}
|
|
237478
|
+
const protPath = resolvedBranch ? `repos/${owner}/${repo}/branches/${encodeURIComponent(resolvedBranch)}/protection` : protectionPath;
|
|
237479
|
+
const protectionRead = client && protPath ? await client.get(protPath) : null;
|
|
237480
|
+
const environmentRead = client && envPath ? await client.get(envPath) : null;
|
|
237481
|
+
return {
|
|
237482
|
+
branch: resolvedBranch,
|
|
237483
|
+
repoRead,
|
|
237484
|
+
protectionRead,
|
|
237485
|
+
environmentRead
|
|
237486
|
+
};
|
|
237487
|
+
}
|
|
237488
|
+
module2.exports = {
|
|
237489
|
+
EVIDENCE_SPEC,
|
|
237490
|
+
APP_INSTALLATION,
|
|
237491
|
+
NO_TOKEN_INSTRUCTION,
|
|
237492
|
+
LAYER_IDS,
|
|
237493
|
+
STATUS,
|
|
237494
|
+
CHECK_NAME,
|
|
237495
|
+
GATE_ACTION,
|
|
237496
|
+
resolveGitHubAuth,
|
|
237497
|
+
redactSecrets,
|
|
237498
|
+
createGitHubHttpClient,
|
|
237499
|
+
evaluateLayers,
|
|
237500
|
+
scanLocalWorkflows,
|
|
237501
|
+
queryGitHubEnforcement,
|
|
237502
|
+
enforceAdminsEnabled,
|
|
237503
|
+
strictEnabled,
|
|
237504
|
+
environmentProtected
|
|
237505
|
+
};
|
|
237506
|
+
}
|
|
237507
|
+
});
|
|
237508
|
+
|
|
237509
|
+
// src/commands/enforce-check.js
|
|
237510
|
+
var require_enforce_check = __commonJS({
|
|
237511
|
+
"src/commands/enforce-check.js"(exports2, module2) {
|
|
237512
|
+
"use strict";
|
|
237513
|
+
var chalk = require_source();
|
|
237514
|
+
var {
|
|
237515
|
+
EVIDENCE_SPEC,
|
|
237516
|
+
APP_INSTALLATION,
|
|
237517
|
+
NO_TOKEN_INSTRUCTION,
|
|
237518
|
+
resolveGitHubAuth,
|
|
237519
|
+
createGitHubHttpClient,
|
|
237520
|
+
evaluateLayers,
|
|
237521
|
+
scanLocalWorkflows,
|
|
237522
|
+
queryGitHubEnforcement
|
|
237523
|
+
} = require_github_enforcement();
|
|
237524
|
+
var { isValidRepo } = require_status();
|
|
237525
|
+
var { resolveOwnerRepo } = require_setup_required_check();
|
|
237526
|
+
if (process.env.NO_COLOR) chalk.level = 0;
|
|
237527
|
+
var USAGE = [
|
|
237528
|
+
"Usage: coderifts enforce --check --repo owner/repo [--env <github-environment>] [--branch <name>] [--json]",
|
|
237529
|
+
"",
|
|
237530
|
+
"Query the provider (GitHub) and local workflow files. Report, per layer:",
|
|
237531
|
+
" VERIFIED / NOT_VERIFIED / UNVERIFIABLE(reason).",
|
|
237532
|
+
"inescapable_deploy is true only when every layer is VERIFIED.",
|
|
237533
|
+
"",
|
|
237534
|
+
"Auth: GITHUB_TOKEN (or GH_TOKEN). Never prints token values.",
|
|
237535
|
+
"The CodeRifts GitHub App installation cannot query branch protection",
|
|
237536
|
+
"(missing administration:read)."
|
|
237537
|
+
].join("\n");
|
|
237538
|
+
function statusColor(status) {
|
|
237539
|
+
if (status === "VERIFIED") return chalk.green(status);
|
|
237540
|
+
if (status === "NOT_VERIFIED") return chalk.yellow(status);
|
|
237541
|
+
return chalk.red(status);
|
|
237542
|
+
}
|
|
237543
|
+
function renderHuman(report) {
|
|
237544
|
+
const lines = [];
|
|
237545
|
+
lines.push(chalk.bold("CodeRifts enforce --check"));
|
|
237546
|
+
lines.push(` repo: ${report.repo}`);
|
|
237547
|
+
lines.push(` branch: ${report.branch || "(unknown)"}`);
|
|
237548
|
+
if (report.environment) lines.push(` env: ${report.environment}`);
|
|
237549
|
+
lines.push(` queried: ${report.queried_at}`);
|
|
237550
|
+
lines.push(` auth: ${report.auth.source} (value not printed)`);
|
|
237551
|
+
lines.push(chalk.dim(` App installation: cannot query protection (${report.auth.app_installation.missing_scope})`));
|
|
237552
|
+
lines.push("");
|
|
237553
|
+
lines.push(chalk.bold(" Layer Status"));
|
|
237554
|
+
for (const l of report.layers) {
|
|
237555
|
+
const id = String(l.id).padEnd(34, " ");
|
|
237556
|
+
lines.push(` ${id}${statusColor(l.status)}`);
|
|
237557
|
+
if (l.status !== "VERIFIED" && l.reason) {
|
|
237558
|
+
lines.push(chalk.dim(` WHY ${l.reason}`));
|
|
237559
|
+
}
|
|
237560
|
+
if (l.endpoint) lines.push(chalk.dim(` via ${l.endpoint}`));
|
|
237561
|
+
}
|
|
237562
|
+
lines.push("");
|
|
237563
|
+
const claim = report.claim && report.claim.inescapable_deploy === true;
|
|
237564
|
+
lines.push(` inescapable_deploy: ${claim ? chalk.green("true") : chalk.yellow("false")}`);
|
|
237565
|
+
lines.push(chalk.dim(` ${report.claim.basis}`));
|
|
237566
|
+
lines.push("");
|
|
237567
|
+
lines.push(chalk.dim(" This is queried evidence, not a host claim."));
|
|
237568
|
+
lines.push(chalk.dim(" --enforce / CODERIFTS_DEPLOY_NO_BYPASS are host claims and do not set this field."));
|
|
237569
|
+
if (!claim) {
|
|
237570
|
+
lines.push("");
|
|
237571
|
+
lines.push(chalk.bold(" ACTION"));
|
|
237572
|
+
const missing = report.layers.filter((l) => l.status !== "VERIFIED");
|
|
237573
|
+
for (const l of missing) {
|
|
237574
|
+
if (l.id === "required_check") {
|
|
237575
|
+
lines.push(chalk.dim(' require "CodeRifts / contract-gate" on default-branch protection'));
|
|
237576
|
+
lines.push(chalk.dim(" (coderifts setup-required-check --apply)"));
|
|
237577
|
+
} else if (l.id === "enforce_admins") {
|
|
237578
|
+
lines.push(chalk.dim(" set enforce_admins: true so administrators cannot bypass"));
|
|
237579
|
+
lines.push(chalk.dim(" (coderifts setup-required-check --enforce-admins --apply)"));
|
|
237580
|
+
} else if (l.id === "strict_status_checks") {
|
|
237581
|
+
lines.push(chalk.dim(" set required_status_checks.strict: true"));
|
|
237582
|
+
} else if (l.id === "environment_protection") {
|
|
237583
|
+
lines.push(chalk.dim(" add a GitHub Environment with protection rules, pass --env <name>"));
|
|
237584
|
+
} else if (l.id === "workflow_contract_gate") {
|
|
237585
|
+
lines.push(chalk.dim(" add .github/workflows/coderifts.yml using coderifts/contract-gate@v0"));
|
|
237586
|
+
lines.push(chalk.dim(" (coderifts init --agents)"));
|
|
237587
|
+
} else if (l.id === "workflow_deploy_gate") {
|
|
237588
|
+
lines.push(chalk.dim(" add a CD step: coderifts deploy-gate --env <env> --artifact <id> --receipt <file>"));
|
|
237589
|
+
}
|
|
237590
|
+
}
|
|
237591
|
+
}
|
|
237592
|
+
return lines.join("\n");
|
|
237593
|
+
}
|
|
237594
|
+
async function runEnforceCheck(options = {}, deps = {}) {
|
|
237595
|
+
const log = deps.log || console.log.bind(console);
|
|
237596
|
+
const errLog = deps.errLog || console.error.bind(console);
|
|
237597
|
+
const cwd = deps.cwd || options.cwd || process.cwd();
|
|
237598
|
+
const now = deps.now || (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
237599
|
+
const env = deps.env || process.env;
|
|
237600
|
+
const repoArg = options.repo;
|
|
237601
|
+
let ownerRepo = null;
|
|
237602
|
+
if (repoArg && isValidRepo(String(repoArg).trim())) {
|
|
237603
|
+
const [owner, repo] = String(repoArg).trim().split("/");
|
|
237604
|
+
ownerRepo = { owner, repo };
|
|
237605
|
+
} else if (!repoArg) {
|
|
237606
|
+
try {
|
|
237607
|
+
ownerRepo = resolveOwnerRepo(cwd, deps.gitImpl);
|
|
237608
|
+
} catch {
|
|
237609
|
+
ownerRepo = null;
|
|
237610
|
+
}
|
|
237611
|
+
}
|
|
237612
|
+
if (!ownerRepo) {
|
|
237613
|
+
errLog(chalk.red("Error: missing repo (pass --repo owner/repo)"));
|
|
237614
|
+
errLog(USAGE);
|
|
237615
|
+
return { exitCode: 1, error: "missing_repo" };
|
|
237616
|
+
}
|
|
237617
|
+
const providerName = String(options.provider || "github").toLowerCase();
|
|
237618
|
+
const queriedAt = now();
|
|
237619
|
+
const environmentName = options.env ? String(options.env).trim() : null;
|
|
237620
|
+
const local = (deps.scanLocalWorkflows || scanLocalWorkflows)(cwd, deps.fs || {});
|
|
237621
|
+
const authPublic = {
|
|
237622
|
+
source: "none",
|
|
237623
|
+
app_installation: { ...APP_INSTALLATION }
|
|
237624
|
+
};
|
|
237625
|
+
if (providerName !== "github") {
|
|
237626
|
+
const reason = `no ${providerName} adapter \u2014 GitHub is the only implemented provider; refusing to stub results`;
|
|
237627
|
+
const evaluated2 = evaluateLayers({
|
|
237628
|
+
protectionRead: null,
|
|
237629
|
+
environmentRead: null,
|
|
237630
|
+
environmentName,
|
|
237631
|
+
local,
|
|
237632
|
+
auth: { ok: false, source: "none", reason }
|
|
237633
|
+
});
|
|
237634
|
+
const report2 = {
|
|
237635
|
+
command: "enforce --check",
|
|
237636
|
+
spec: EVIDENCE_SPEC,
|
|
237637
|
+
queried_at: queriedAt,
|
|
237638
|
+
repo: `${ownerRepo.owner}/${ownerRepo.repo}`,
|
|
237639
|
+
branch: options.branch || null,
|
|
237640
|
+
environment: environmentName,
|
|
237641
|
+
provider: providerName,
|
|
237642
|
+
auth: { ...authPublic, reason },
|
|
237643
|
+
...evaluated2
|
|
237644
|
+
};
|
|
237645
|
+
if (options.json) log(JSON.stringify(report2, null, 2));
|
|
237646
|
+
else log(renderHuman(report2));
|
|
237647
|
+
return { exitCode: 0, report: report2 };
|
|
237648
|
+
}
|
|
237649
|
+
const auth = (deps.resolveAuth || resolveGitHubAuth)(env);
|
|
237650
|
+
authPublic.source = auth.source;
|
|
237651
|
+
if (auth.reason) authPublic.reason = auth.reason;
|
|
237652
|
+
let branch = options.branch ? String(options.branch).trim() : null;
|
|
237653
|
+
let protectionRead = null;
|
|
237654
|
+
let environmentRead = null;
|
|
237655
|
+
if (auth.ok) {
|
|
237656
|
+
const client = deps.githubClient || createGitHubHttpClient({
|
|
237657
|
+
token: auth.token,
|
|
237658
|
+
fetchImpl: deps.fetchImpl
|
|
237659
|
+
});
|
|
237660
|
+
const queried = await queryGitHubEnforcement({
|
|
237661
|
+
owner: ownerRepo.owner,
|
|
237662
|
+
repo: ownerRepo.repo,
|
|
237663
|
+
branch,
|
|
237664
|
+
environmentName,
|
|
237665
|
+
client
|
|
237666
|
+
});
|
|
237667
|
+
branch = queried.branch || branch;
|
|
237668
|
+
protectionRead = queried.protectionRead;
|
|
237669
|
+
environmentRead = queried.environmentRead;
|
|
237670
|
+
}
|
|
237671
|
+
const evaluated = evaluateLayers({
|
|
237672
|
+
protectionRead,
|
|
237673
|
+
environmentRead,
|
|
237674
|
+
environmentName,
|
|
237675
|
+
local,
|
|
237676
|
+
auth
|
|
237677
|
+
});
|
|
237678
|
+
const report = {
|
|
237679
|
+
command: "enforce --check",
|
|
237680
|
+
spec: EVIDENCE_SPEC,
|
|
237681
|
+
queried_at: queriedAt,
|
|
237682
|
+
repo: `${ownerRepo.owner}/${ownerRepo.repo}`,
|
|
237683
|
+
branch,
|
|
237684
|
+
environment: environmentName,
|
|
237685
|
+
provider: "github",
|
|
237686
|
+
auth: authPublic,
|
|
237687
|
+
...evaluated
|
|
237688
|
+
};
|
|
237689
|
+
if (options.json) log(JSON.stringify(report, null, 2));
|
|
237690
|
+
else log(renderHuman(report));
|
|
237691
|
+
return { exitCode: 0, report };
|
|
237692
|
+
}
|
|
237693
|
+
module2.exports = {
|
|
237694
|
+
runEnforceCheck,
|
|
237695
|
+
renderHuman,
|
|
237696
|
+
USAGE,
|
|
237697
|
+
NO_TOKEN_INSTRUCTION
|
|
237698
|
+
};
|
|
237699
|
+
}
|
|
237700
|
+
});
|
|
237701
|
+
|
|
237002
237702
|
// src/commands/enforce.js
|
|
237003
237703
|
var require_enforce = __commonJS({
|
|
237004
237704
|
"src/commands/enforce.js"(exports2, module2) {
|
|
@@ -237021,14 +237721,19 @@ var require_enforce = __commonJS({
|
|
|
237021
237721
|
var USAGE = [
|
|
237022
237722
|
"Usage: coderifts enforce --repo owner/repo [--apply]",
|
|
237023
237723
|
" or: coderifts enforce owner/repo [--apply]",
|
|
237724
|
+
" or: coderifts enforce --check --repo owner/repo [--env <name>] [--json]",
|
|
237024
237725
|
"",
|
|
237025
237726
|
"Cross-layer orchestrator: reads enforcement-status, then closes gaps by calling existing",
|
|
237026
237727
|
"setup commands (setup-required-check, hook install, deploy-gate guidance).",
|
|
237027
237728
|
"",
|
|
237729
|
+
"--check: query GitHub + local workflows and report VERIFIED / NOT_VERIFIED /",
|
|
237730
|
+
"UNVERIFIABLE per layer. inescapable_deploy is true only when every layer is VERIFIED.",
|
|
237731
|
+
"Does not use the CodeRifts API key. Auth: GITHUB_TOKEN. Read-only.",
|
|
237732
|
+
"",
|
|
237028
237733
|
"DRY-RUN BY DEFAULT \u2014 without --apply, prints what WOULD run and mutates NOTHING.",
|
|
237029
237734
|
"With --apply, threads apply into each underlying command (their own safety still applies).",
|
|
237030
237735
|
"",
|
|
237031
|
-
"Requires a cloud API key for the status read (coderifts login / CODERIFTS_API_KEY).",
|
|
237736
|
+
"Requires a cloud API key for the (non --check) status read (coderifts login / CODERIFTS_API_KEY).",
|
|
237032
237737
|
"Merge apply uses your local `gh` credentials, not the CodeRifts API key."
|
|
237033
237738
|
].join("\n");
|
|
237034
237739
|
var AGENT_GUARD_GUIDANCE = [
|
|
@@ -237039,8 +237744,10 @@ var require_enforce = __commonJS({
|
|
|
237039
237744
|
].join("\n");
|
|
237040
237745
|
var DEPLOY_GUIDANCE = [
|
|
237041
237746
|
"Deploy is declared-only on the server (never server-observed ENFORCING).",
|
|
237042
|
-
"Add a CD step that runs: coderifts deploy-gate --env <env> --artifact <id> --receipt <file>
|
|
237043
|
-
" (fail-closed by default
|
|
237747
|
+
"Add a CD step that runs: coderifts deploy-gate --env <env> --artifact <id> --receipt <file>",
|
|
237748
|
+
" (fail-closed by default. --enforce is a host claim, not inescapable_deploy.",
|
|
237749
|
+
" Prove pipeline protection with: coderifts enforce --check --repo owner/repo --env <env>.",
|
|
237750
|
+
" CODERIFTS_DEPLOY_ADVISORY=1 to soften the exit.)",
|
|
237044
237751
|
"Also set policy.require_source_binding: true in .coderifts.yml for the deploy declaration leg."
|
|
237045
237752
|
].join("\n");
|
|
237046
237753
|
var CONTENT_GUIDANCE = [
|
|
@@ -237067,6 +237774,15 @@ var require_enforce = __commonJS({
|
|
|
237067
237774
|
}
|
|
237068
237775
|
}
|
|
237069
237776
|
async function runEnforce(options = {}, deps = {}) {
|
|
237777
|
+
if (options.check === true) {
|
|
237778
|
+
if (options.apply === true) {
|
|
237779
|
+
const errLog2 = deps.errLog || console.error.bind(console);
|
|
237780
|
+
errLog2(chalk.red("Error: --check is read-only evidence and cannot be combined with --apply"));
|
|
237781
|
+
return { exitCode: 1, error: "check_apply_conflict", outcomes: [] };
|
|
237782
|
+
}
|
|
237783
|
+
const { runEnforceCheck } = require_enforce_check();
|
|
237784
|
+
return runEnforceCheck(options, deps);
|
|
237785
|
+
}
|
|
237070
237786
|
const apply = options.apply === true;
|
|
237071
237787
|
const getKey = deps.getApiKey || getApiKey;
|
|
237072
237788
|
const fetchStatus = deps.cloudGetEnforcementStatus || cloudGetEnforcementStatus;
|
|
@@ -238206,7 +238922,7 @@ var require_outcome = __commonJS({
|
|
|
238206
238922
|
var require_cursor_hook = __commonJS({
|
|
238207
238923
|
"src/commands/cursor-hook.js"(exports2, module2) {
|
|
238208
238924
|
"use strict";
|
|
238209
|
-
var { runClaudeHook } = require_claude_hook();
|
|
238925
|
+
var { runClaudeHook, pickToolField, filePathFromInput, contentFromInput, coerceToolInput } = require_claude_hook();
|
|
238210
238926
|
var USAGE = `Usage: coderifts cursor-hook
|
|
238211
238927
|
|
|
238212
238928
|
Cursor preToolUse adapter. Reads Cursor's hook JSON on stdin, emits
|
|
@@ -238233,15 +238949,15 @@ Unparseable stdin denies (fail-closed).
|
|
|
238233
238949
|
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
|
|
238234
238950
|
return { ok: false, reason: "stdin JSON is not an object" };
|
|
238235
238951
|
}
|
|
238236
|
-
const toolName =
|
|
238237
|
-
if (!toolName) return { ok: false, reason: "missing tool_name" };
|
|
238238
|
-
const toolInput = obj
|
|
238952
|
+
const toolName = pickToolField(obj, "tool_name", "toolName", "name");
|
|
238953
|
+
if (typeof toolName !== "string" || !toolName) return { ok: false, reason: "missing tool_name" };
|
|
238954
|
+
const toolInput = coerceToolInput(pickToolField(obj, "tool_input", "toolInput", "input")) || (filePathFromInput(obj) ? obj : null);
|
|
238239
238955
|
if (!toolInput) return { ok: false, reason: "missing tool_input" };
|
|
238240
238956
|
return { ok: true, payload: obj, toolName, toolInput };
|
|
238241
238957
|
}
|
|
238242
238958
|
function toClaudeShape(toolName, toolInput) {
|
|
238243
|
-
const filePath = toolInput
|
|
238244
|
-
const content = toolInput
|
|
238959
|
+
const filePath = filePathFromInput(toolInput) || null;
|
|
238960
|
+
const content = contentFromInput(toolInput);
|
|
238245
238961
|
const out = { ...toolInput };
|
|
238246
238962
|
if (filePath) out.file_path = filePath;
|
|
238247
238963
|
if (content !== void 0) out.content = content;
|
|
@@ -240003,7 +240719,7 @@ program.command("registry-gate [dir]").description("Admit a directory of OpenAPI
|
|
|
240003
240719
|
process.exitCode = code;
|
|
240004
240720
|
process.exit(code);
|
|
240005
240721
|
});
|
|
240006
|
-
program.command("init [template]").description("Generate a .coderifts.yml from a policy template, or wire governed agent work (--agents)").option("--agents", "Wire this repo for governed agent work (MCP config, rule files, host hook, CI gate)").option("--hosts <list>", "With --agents: claude, cursor, or all (default: all)", "all").option("--no-hook", "With --agents: skip host hook install").option("--no-workflow", "With --agents: skip writing .github/workflows/coderifts.yml").option("--dry-run", "With --agents: print the plan without writing").option("--check", "With --agents: report which of the four pieces are present (no write)").option("--out <dir>", "With --agents: target directory (default: cwd)").action(async (template, options) => {
|
|
240722
|
+
program.command("init [template]").description("Generate a .coderifts.yml from a policy template, or wire governed agent work (--agents)").option("--agents", "Wire this repo for governed agent work (MCP config, rule files, host hook, CI gate)").option("--hosts <list>", "With --agents: claude, cursor, copilot, or all (default: all)", "all").option("--no-hook", "With --agents: skip host hook install").option("--no-workflow", "With --agents: skip writing .github/workflows/coderifts.yml").option("--dry-run", "With --agents: print the plan without writing").option("--check", "With --agents: report which of the four pieces are present (no write)").option("--out <dir>", "With --agents: target directory (default: cwd)").action(async (template, options) => {
|
|
240007
240723
|
const {
|
|
240008
240724
|
runInitAgents,
|
|
240009
240725
|
agentFlagsRequireAgents,
|
|
@@ -240058,12 +240774,13 @@ program.command("status [repo]").description("Show cross-layer enforcement statu
|
|
|
240058
240774
|
process.exitCode = result.exitCode;
|
|
240059
240775
|
}
|
|
240060
240776
|
});
|
|
240061
|
-
program.command("enforce [repo]").description("Close enforcement gaps
|
|
240777
|
+
program.command("enforce [repo]").description("Close enforcement gaps (dry-run default), or --check to query provider-native pipeline protection evidence").option("--repo <owner/repo>", "Repository (owner/repo); also accepted as a positional argument").option("--apply", "Actually run underlying setup commands (default: dry-run only)").option("--check", "Query GitHub + local workflows; report VERIFIED / NOT_VERIFIED / UNVERIFIABLE (read-only)").option("--env <name>", "With --check: GitHub Environment name to query for protection rules").option("--branch <name>", "With --check: branch to query (default: repository default branch)").option("--provider <name>", "With --check: provider adapter (github only; others UNVERIFIABLE, never stubbed)", "github").option("--json", "Machine-readable JSON result").action(async (repoPositional, options) => {
|
|
240062
240778
|
const { runEnforce } = require_enforce();
|
|
240063
240779
|
const result = await runEnforce({
|
|
240064
240780
|
...options,
|
|
240065
240781
|
repo: options.repo || repoPositional || null,
|
|
240066
|
-
apply: !!options.apply
|
|
240782
|
+
apply: !!options.apply,
|
|
240783
|
+
check: !!options.check
|
|
240067
240784
|
});
|
|
240068
240785
|
if (result && typeof result.exitCode === "number") {
|
|
240069
240786
|
process.exitCode = result.exitCode;
|