coderifts 4.6.0 → 4.7.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 +33 -0
- package/README.md +36 -2
- package/bin/coderifts.js +6 -1
- package/dist/cli.js +572 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.7.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **`coderifts enforce --check`** — provider-native CD enforcement evidence.
|
|
7
|
+
Queries GitHub (and local workflow files) and reports, per layer,
|
|
8
|
+
`VERIFIED` / `NOT_VERIFIED` / `UNVERIFIABLE(reason)`:
|
|
9
|
+
1. required check `CodeRifts / contract-gate` on default-branch protection
|
|
10
|
+
2. `enforce_admins` (admin bypass)
|
|
11
|
+
3. `required_status_checks.strict`
|
|
12
|
+
4. GitHub Environment protection rules (`--env`)
|
|
13
|
+
5. workflow references `coderifts/contract-gate@v0` (local)
|
|
14
|
+
6. workflow runs `coderifts deploy-gate` (local)
|
|
15
|
+
|
|
16
|
+
`inescapable_deploy` is **true only when every layer is VERIFIED**. Otherwise
|
|
17
|
+
the field stays false and the JSON carries the queried facts. `--json`
|
|
18
|
+
includes each layer's status, the API endpoint consulted, and `queried_at`.
|
|
19
|
+
|
|
20
|
+
Auth: `GITHUB_TOKEN` / `GH_TOKEN` (user). App installation token
|
|
21
|
+
(`GITHUB_APP_TOKEN`) is accepted but **cannot** query branch protection —
|
|
22
|
+
the GitHub App permission set is contents:read, pull_requests, checks
|
|
23
|
+
(AUDIT.md §2.4); `GET .../protection` needs `administration:read`. No
|
|
24
|
+
token → `UNVERIFIABLE` with the exact instruction. Token values are never
|
|
25
|
+
printed. GitLab/Bitbucket: no adapter (UNVERIFIABLE, never stubbed).
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
- **`observeCDEnforcement`:** `CODERIFTS_DEPLOY_NO_BYPASS` is recorded as
|
|
29
|
+
`host_claim_no_bypass` and no longer sets `bypass_possible: false`.
|
|
30
|
+
`--enforce` still marks the step ENFORCING (exit is already fail-closed)
|
|
31
|
+
but is a **host claim**. `bypass_possible` is false only when
|
|
32
|
+
`providerEvidence.inescapable_deploy === true` from `enforce --check`.
|
|
33
|
+
Guard `deployGate` still consumes `{ enforcement, bypass_possible }` — the
|
|
34
|
+
CLI must not pass a flag-derived “no bypass”.
|
|
35
|
+
|
|
3
36
|
## 4.6.0
|
|
4
37
|
|
|
5
38
|
**Version pick:** the parallel Cursor-hook round claimed **4.5.0**
|
package/README.md
CHANGED
|
@@ -168,8 +168,11 @@ gate reason + `renderDecisionWhy` when a body exists) and
|
|
|
168
168
|
unsigned file still prints the unsigned-receipt truth loudly.
|
|
169
169
|
|
|
170
170
|
**`--enforce` / `CODERIFTS_DEPLOY_ENFORCE=true`:** accepted silently (no-op
|
|
171
|
-
for exit — default is already fail-closed).
|
|
172
|
-
`inescapable_deploy
|
|
171
|
+
for exit — default is already fail-closed). This is a **host claim** that the
|
|
172
|
+
step is ENFORCING. It does **not** set `inescapable_deploy`. That field is
|
|
173
|
+
true only from queried pipeline protection (`coderifts enforce --check`).
|
|
174
|
+
`CODERIFTS_DEPLOY_NO_BYPASS` is recorded as a host claim and does not clear
|
|
175
|
+
`bypass_possible`.
|
|
173
176
|
|
|
174
177
|
**Infra vs policy:** missing `--env` / `--artifact`, malformed receipt JSON, or
|
|
175
178
|
an unreachable `--keys` registry prints distinct `INFRA` wording plus
|
|
@@ -382,13 +385,44 @@ failed, **1** on missing repo/key, API error, or any apply failure.
|
|
|
382
385
|
|------|-------------|---------|
|
|
383
386
|
| `[repo]` / `--repo <owner/repo>` | Repository | none (required) |
|
|
384
387
|
| `--apply` | Run underlying setup commands | dry-run |
|
|
388
|
+
| `--check` | Query GitHub + local workflows (read-only evidence) | `false` |
|
|
389
|
+
| `--env <name>` | With `--check`: GitHub Environment to query | none |
|
|
390
|
+
| `--branch <name>` | With `--check`: branch (default: repo default) | default branch |
|
|
385
391
|
| `--json` | Machine-readable JSON result | `false` |
|
|
386
392
|
|
|
387
393
|
```bash
|
|
388
394
|
coderifts enforce owner/repo
|
|
389
395
|
coderifts enforce --repo owner/repo --apply
|
|
396
|
+
coderifts enforce --check --repo owner/repo --env production --json
|
|
390
397
|
```
|
|
391
398
|
|
|
399
|
+
### `coderifts enforce --check`
|
|
400
|
+
|
|
401
|
+
Provider-native pipeline-protection evidence. This is **not** a host claim:
|
|
402
|
+
`--enforce` and `CODERIFTS_DEPLOY_NO_BYPASS` do **not** set
|
|
403
|
+
`inescapable_deploy`. The field is true only when every layer below is
|
|
404
|
+
`VERIFIED`.
|
|
405
|
+
|
|
406
|
+
| Layer | VERIFIED when |
|
|
407
|
+
|-------|----------------|
|
|
408
|
+
| `required_check` | Default-branch protection requires `CodeRifts / contract-gate` |
|
|
409
|
+
| `enforce_admins` | `enforce_admins.enabled` is **true** (admins cannot bypass). `false` = NOT_VERIFIED (admin bypass open) |
|
|
410
|
+
| `strict_status_checks` | `required_status_checks.strict` is true |
|
|
411
|
+
| `environment_protection` | `--env` names a GitHub Environment with protection rules or a deployment branch policy |
|
|
412
|
+
| `workflow_contract_gate` | a local `.github/workflows` file references `coderifts/contract-gate@v0` |
|
|
413
|
+
| `workflow_deploy_gate` | a local workflow runs `coderifts deploy-gate` |
|
|
414
|
+
|
|
415
|
+
Statuses: `VERIFIED` / `NOT_VERIFIED` / `UNVERIFIABLE(reason)`. Auth:
|
|
416
|
+
`GITHUB_TOKEN` (never printed). **No token** → API layers `UNVERIFIABLE` with
|
|
417
|
+
the instruction to set `GITHUB_TOKEN` (repo + `administration:read`). The
|
|
418
|
+
CodeRifts GitHub App **cannot** query branch protection (permissions are
|
|
419
|
+
contents / pull requests / checks — not `administration:read`).
|
|
420
|
+
|
|
421
|
+
`--json` is the quotable evidence document (`spec`:
|
|
422
|
+
`provider-enforcement-evidence.v1`): each layer's status, the API endpoint
|
|
423
|
+
consulted, `queried_at`. Exit **0** when a report was produced (evidence, not
|
|
424
|
+
a gate). GitLab/Bitbucket: no adapter — `UNVERIFIABLE`, never stubbed.
|
|
425
|
+
|
|
392
426
|
### Agent host files (`coderifts agent-setup`)
|
|
393
427
|
|
|
394
428
|
Writes the six CodeRifts agent-host rule files **plus** the Claude Code
|
package/bin/coderifts.js
CHANGED
|
@@ -184,9 +184,13 @@ program
|
|
|
184
184
|
// No new GitHub writes with the CodeRifts API key (user's gh for merge; local fs for hook).
|
|
185
185
|
program
|
|
186
186
|
.command('enforce [repo]')
|
|
187
|
-
.description('Close enforcement gaps
|
|
187
|
+
.description('Close enforcement gaps (dry-run default), or --check to query provider-native pipeline protection evidence')
|
|
188
188
|
.option('--repo <owner/repo>', 'Repository (owner/repo); also accepted as a positional argument')
|
|
189
189
|
.option('--apply', 'Actually run underlying setup commands (default: dry-run only)')
|
|
190
|
+
.option('--check', 'Query GitHub + local workflows; report VERIFIED / NOT_VERIFIED / UNVERIFIABLE (read-only)')
|
|
191
|
+
.option('--env <name>', 'With --check: GitHub Environment name to query for protection rules')
|
|
192
|
+
.option('--branch <name>', 'With --check: branch to query (default: repository default branch)')
|
|
193
|
+
.option('--provider <name>', 'With --check: provider adapter (github only; others UNVERIFIABLE, never stubbed)', 'github')
|
|
190
194
|
.option('--json', 'Machine-readable JSON result')
|
|
191
195
|
.action(async (repoPositional, options) => {
|
|
192
196
|
const { runEnforce } = require('../src/commands/enforce');
|
|
@@ -194,6 +198,7 @@ program
|
|
|
194
198
|
...options,
|
|
195
199
|
repo: options.repo || repoPositional || null,
|
|
196
200
|
apply: !!options.apply,
|
|
201
|
+
check: !!options.check,
|
|
197
202
|
});
|
|
198
203
|
if (result && typeof result.exitCode === 'number') {
|
|
199
204
|
process.exitCode = result.exitCode;
|
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.7.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",
|
|
@@ -207461,18 +207461,31 @@ var require_deploy_gate2 = __commonJS({
|
|
|
207461
207461
|
return isEnvFlag(e, "CODERIFTS_DEPLOY_ADVISORY") || isEnvFlag(e, "CODERIFTS_ADVISORY");
|
|
207462
207462
|
}
|
|
207463
207463
|
function observeCDEnforcement(options = {}) {
|
|
207464
|
+
const evidence = options.providerEvidence;
|
|
207465
|
+
const hostClaimNoBypass = String(process.env.CODERIFTS_DEPLOY_NO_BYPASS || "").toLowerCase() === "true";
|
|
207466
|
+
if (evidence && evidence.inescapable_deploy === true) {
|
|
207467
|
+
return {
|
|
207468
|
+
enforcement: "ENFORCING",
|
|
207469
|
+
bypass_possible: false,
|
|
207470
|
+
step_is_required: true,
|
|
207471
|
+
required_step_name: "CodeRifts / deploy-gate",
|
|
207472
|
+
attestation_source: "provider_query",
|
|
207473
|
+
host_claim_no_bypass: hostClaimNoBypass
|
|
207474
|
+
};
|
|
207475
|
+
}
|
|
207464
207476
|
const envVal = String(process.env.CODERIFTS_DEPLOY_ENFORCE || "").toLowerCase();
|
|
207465
207477
|
let enforcement;
|
|
207466
207478
|
if (enforceSignal(options)) enforcement = "ENFORCING";
|
|
207467
207479
|
else if (envVal === "unknown") enforcement = "UNKNOWN";
|
|
207468
207480
|
else enforcement = "ADVISORY";
|
|
207469
|
-
const bypass_possible = String(process.env.CODERIFTS_DEPLOY_NO_BYPASS || "").toLowerCase() !== "true";
|
|
207470
207481
|
return {
|
|
207471
207482
|
enforcement,
|
|
207472
|
-
bypass_possible,
|
|
207483
|
+
bypass_possible: true,
|
|
207484
|
+
// host NO_BYPASS is a claim, not queried evidence
|
|
207473
207485
|
step_is_required: enforcement === "ENFORCING",
|
|
207474
207486
|
required_step_name: "CodeRifts / deploy-gate",
|
|
207475
|
-
attestation_source: "cli_flag"
|
|
207487
|
+
attestation_source: "cli_flag",
|
|
207488
|
+
host_claim_no_bypass: hostClaimNoBypass
|
|
207476
207489
|
};
|
|
207477
207490
|
}
|
|
207478
207491
|
function deployReportResiduals(state, enforcement_inescapable, enforcement, change_set_rebound) {
|
|
@@ -207795,7 +207808,7 @@ var require_deploy_gate2 = __commonJS({
|
|
|
207795
207808
|
if (bind.report_residuals.length) lines.push(` Residuals: ${bind.report_residuals.join(", ")}`);
|
|
207796
207809
|
if (bind.must_re_preflight) lines.push(chalk.yellow(" Action: re-preflight this { environment, artifact } \u2014 the receipt does not authorize it."));
|
|
207797
207810
|
lines.push("");
|
|
207798
|
-
lines.push(enforce ? chalk.dim(" Attested ENFORCING (
|
|
207811
|
+
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
207812
|
lines.push("");
|
|
207800
207813
|
return lines.join("\n");
|
|
207801
207814
|
}
|
|
@@ -236999,6 +237012,538 @@ var require_status = __commonJS({
|
|
|
236999
237012
|
}
|
|
237000
237013
|
});
|
|
237001
237014
|
|
|
237015
|
+
// src/provider/github-enforcement.js
|
|
237016
|
+
var require_github_enforcement = __commonJS({
|
|
237017
|
+
"src/provider/github-enforcement.js"(exports2, module2) {
|
|
237018
|
+
"use strict";
|
|
237019
|
+
var fs = require("fs");
|
|
237020
|
+
var path = require("path");
|
|
237021
|
+
var {
|
|
237022
|
+
CHECK_NAME,
|
|
237023
|
+
extractRequiredContexts
|
|
237024
|
+
} = require_setup_required_check();
|
|
237025
|
+
var { GATE_ACTION } = require_contract_gate_workflow();
|
|
237026
|
+
var EVIDENCE_SPEC = "provider-enforcement-evidence.v1";
|
|
237027
|
+
var APP_INSTALLATION = Object.freeze({
|
|
237028
|
+
can_query_protection: false,
|
|
237029
|
+
permissions: Object.freeze([
|
|
237030
|
+
"contents:read",
|
|
237031
|
+
"pull_requests:read+write",
|
|
237032
|
+
"checks:read+write"
|
|
237033
|
+
]),
|
|
237034
|
+
missing_scope: "administration:read",
|
|
237035
|
+
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."
|
|
237036
|
+
});
|
|
237037
|
+
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).";
|
|
237038
|
+
var LAYER_IDS = Object.freeze([
|
|
237039
|
+
"required_check",
|
|
237040
|
+
"enforce_admins",
|
|
237041
|
+
"strict_status_checks",
|
|
237042
|
+
"environment_protection",
|
|
237043
|
+
"workflow_contract_gate",
|
|
237044
|
+
"workflow_deploy_gate"
|
|
237045
|
+
]);
|
|
237046
|
+
var STATUS = Object.freeze({
|
|
237047
|
+
VERIFIED: "VERIFIED",
|
|
237048
|
+
NOT_VERIFIED: "NOT_VERIFIED",
|
|
237049
|
+
UNVERIFIABLE: "UNVERIFIABLE"
|
|
237050
|
+
});
|
|
237051
|
+
function resolveGitHubAuth(env = process.env) {
|
|
237052
|
+
const user = String(env && (env.GITHUB_TOKEN || env.GH_TOKEN) || "").trim();
|
|
237053
|
+
if (user) {
|
|
237054
|
+
return { ok: true, source: "github_token", token: user };
|
|
237055
|
+
}
|
|
237056
|
+
const app = String(env && (env.GITHUB_APP_TOKEN || env.CODERIFTS_GITHUB_APP_TOKEN) || "").trim();
|
|
237057
|
+
if (app) {
|
|
237058
|
+
return { ok: true, source: "github_app_installation", token: app };
|
|
237059
|
+
}
|
|
237060
|
+
return { ok: false, source: "none", token: null, reason: NO_TOKEN_INSTRUCTION };
|
|
237061
|
+
}
|
|
237062
|
+
function redactSecrets(text) {
|
|
237063
|
+
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]");
|
|
237064
|
+
}
|
|
237065
|
+
function createGitHubHttpClient(opts = {}) {
|
|
237066
|
+
const token = opts.token;
|
|
237067
|
+
const fetchFn = opts.fetchImpl || (typeof fetch === "function" ? fetch.bind(globalThis) : null);
|
|
237068
|
+
const apiBase = (opts.apiBase || "https://api.github.com").replace(/\/$/, "");
|
|
237069
|
+
return {
|
|
237070
|
+
name: "github",
|
|
237071
|
+
async get(apiPath) {
|
|
237072
|
+
const endpoint = `${apiBase}/${String(apiPath).replace(/^\//, "")}`;
|
|
237073
|
+
if (!fetchFn) {
|
|
237074
|
+
return {
|
|
237075
|
+
status: 0,
|
|
237076
|
+
body: null,
|
|
237077
|
+
endpoint,
|
|
237078
|
+
error: "fetch is not available in this runtime"
|
|
237079
|
+
};
|
|
237080
|
+
}
|
|
237081
|
+
const res = await fetchFn(endpoint, {
|
|
237082
|
+
method: "GET",
|
|
237083
|
+
headers: {
|
|
237084
|
+
Authorization: `Bearer ${token}`,
|
|
237085
|
+
Accept: "application/vnd.github+json",
|
|
237086
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
237087
|
+
"User-Agent": "coderifts-cli"
|
|
237088
|
+
}
|
|
237089
|
+
});
|
|
237090
|
+
const text = await res.text();
|
|
237091
|
+
let body = null;
|
|
237092
|
+
try {
|
|
237093
|
+
body = text ? JSON.parse(text) : null;
|
|
237094
|
+
} catch {
|
|
237095
|
+
body = null;
|
|
237096
|
+
}
|
|
237097
|
+
return { status: res.status, body, endpoint };
|
|
237098
|
+
}
|
|
237099
|
+
};
|
|
237100
|
+
}
|
|
237101
|
+
function enforceAdminsEnabled(protection) {
|
|
237102
|
+
if (!protection || typeof protection !== "object") return false;
|
|
237103
|
+
const ea = protection.enforce_admins;
|
|
237104
|
+
if (ea === true) return true;
|
|
237105
|
+
if (ea && typeof ea === "object" && ea.enabled === true) return true;
|
|
237106
|
+
return false;
|
|
237107
|
+
}
|
|
237108
|
+
function strictEnabled(protection) {
|
|
237109
|
+
if (!protection || typeof protection !== "object") return false;
|
|
237110
|
+
const rsc = protection.required_status_checks;
|
|
237111
|
+
return !!(rsc && typeof rsc === "object" && rsc.strict === true);
|
|
237112
|
+
}
|
|
237113
|
+
function environmentProtected(body) {
|
|
237114
|
+
if (!body || typeof body !== "object") return false;
|
|
237115
|
+
const rules = Array.isArray(body.protection_rules) ? body.protection_rules : [];
|
|
237116
|
+
if (rules.length > 0) return true;
|
|
237117
|
+
const policy = body.deployment_branch_policy;
|
|
237118
|
+
if (policy && typeof policy === "object") {
|
|
237119
|
+
return policy.protected_branches === true || policy.custom_branch_policies === true;
|
|
237120
|
+
}
|
|
237121
|
+
return false;
|
|
237122
|
+
}
|
|
237123
|
+
function layer(id, status, extra = {}) {
|
|
237124
|
+
return { id, status, ...extra };
|
|
237125
|
+
}
|
|
237126
|
+
function evaluateLayers({
|
|
237127
|
+
protectionRead,
|
|
237128
|
+
environmentRead,
|
|
237129
|
+
environmentName,
|
|
237130
|
+
local,
|
|
237131
|
+
auth
|
|
237132
|
+
} = {}) {
|
|
237133
|
+
const layers = [];
|
|
237134
|
+
const localFiles = local || { contractGateFiles: [], deployGateFiles: [] };
|
|
237135
|
+
const noAuth = !auth || auth.source === "none" || !auth.ok;
|
|
237136
|
+
const prot = protectionRead || null;
|
|
237137
|
+
if (noAuth && !prot) {
|
|
237138
|
+
const why = auth && auth.reason || NO_TOKEN_INSTRUCTION;
|
|
237139
|
+
for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
|
|
237140
|
+
layers.push(layer(id, STATUS.UNVERIFIABLE, {
|
|
237141
|
+
reason: why,
|
|
237142
|
+
endpoint: null,
|
|
237143
|
+
missing_scope: APP_INSTALLATION.missing_scope
|
|
237144
|
+
}));
|
|
237145
|
+
}
|
|
237146
|
+
} else if (!prot) {
|
|
237147
|
+
const why = "branch protection was not queried";
|
|
237148
|
+
for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
|
|
237149
|
+
layers.push(layer(id, STATUS.UNVERIFIABLE, { reason: why, endpoint: null }));
|
|
237150
|
+
}
|
|
237151
|
+
} else if (prot.status === 403) {
|
|
237152
|
+
const why = "GitHub HTTP 403 \u2014 missing scope administration:read (branch protection). Grant Administration: Read on the token, or use a repo-admin token.";
|
|
237153
|
+
const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
|
|
237154
|
+
for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
|
|
237155
|
+
layers.push(layer(id, STATUS.UNVERIFIABLE, {
|
|
237156
|
+
reason: why,
|
|
237157
|
+
endpoint,
|
|
237158
|
+
missing_scope: "administration:read",
|
|
237159
|
+
http_status: 403
|
|
237160
|
+
}));
|
|
237161
|
+
}
|
|
237162
|
+
} else if (prot.status === 404) {
|
|
237163
|
+
const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
|
|
237164
|
+
layers.push(layer("required_check", STATUS.NOT_VERIFIED, {
|
|
237165
|
+
reason: `classic branch protection is absent \u2014 required check "${CHECK_NAME}" is not required`,
|
|
237166
|
+
endpoint,
|
|
237167
|
+
http_status: 404
|
|
237168
|
+
}));
|
|
237169
|
+
layers.push(layer("enforce_admins", STATUS.NOT_VERIFIED, {
|
|
237170
|
+
reason: "classic branch protection is absent \u2014 administrators are unrestricted",
|
|
237171
|
+
endpoint,
|
|
237172
|
+
http_status: 404
|
|
237173
|
+
}));
|
|
237174
|
+
layers.push(layer("strict_status_checks", STATUS.NOT_VERIFIED, {
|
|
237175
|
+
reason: "classic branch protection is absent \u2014 required_status_checks.strict is not set",
|
|
237176
|
+
endpoint,
|
|
237177
|
+
http_status: 404
|
|
237178
|
+
}));
|
|
237179
|
+
} else if (prot.status >= 400 || prot.status === 0) {
|
|
237180
|
+
const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
|
|
237181
|
+
const why = `GitHub HTTP ${prot.status || 0} reading branch protection \u2014 cannot verify`;
|
|
237182
|
+
for (const id of ["required_check", "enforce_admins", "strict_status_checks"]) {
|
|
237183
|
+
layers.push(layer(id, STATUS.UNVERIFIABLE, {
|
|
237184
|
+
reason: redactSecrets(why),
|
|
237185
|
+
endpoint,
|
|
237186
|
+
http_status: prot.status
|
|
237187
|
+
}));
|
|
237188
|
+
}
|
|
237189
|
+
} else {
|
|
237190
|
+
const protection = prot.body && typeof prot.body === "object" ? prot.body : {};
|
|
237191
|
+
const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
|
|
237192
|
+
const contexts = extractRequiredContexts(protection);
|
|
237193
|
+
const hasCheck = contexts.includes(CHECK_NAME);
|
|
237194
|
+
layers.push(layer("required_check", hasCheck ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
237195
|
+
reason: hasCheck ? `required_status_checks includes "${CHECK_NAME}"` : `required_status_checks does not include "${CHECK_NAME}" (have: ${contexts.join(", ") || "none"})`,
|
|
237196
|
+
endpoint,
|
|
237197
|
+
required_contexts: contexts
|
|
237198
|
+
}));
|
|
237199
|
+
const admins = enforceAdminsEnabled(protection);
|
|
237200
|
+
layers.push(layer("enforce_admins", admins ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
237201
|
+
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",
|
|
237202
|
+
endpoint,
|
|
237203
|
+
enforce_admins: admins
|
|
237204
|
+
}));
|
|
237205
|
+
const strict = strictEnabled(protection);
|
|
237206
|
+
layers.push(layer("strict_status_checks", strict ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
237207
|
+
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",
|
|
237208
|
+
endpoint,
|
|
237209
|
+
strict
|
|
237210
|
+
}));
|
|
237211
|
+
}
|
|
237212
|
+
if (!environmentName) {
|
|
237213
|
+
layers.push(layer("environment_protection", STATUS.UNVERIFIABLE, {
|
|
237214
|
+
reason: "no --env supplied; pass --env <GitHub Environment name> to query environment protection rules",
|
|
237215
|
+
endpoint: null
|
|
237216
|
+
}));
|
|
237217
|
+
} else if (noAuth && !environmentRead) {
|
|
237218
|
+
layers.push(layer("environment_protection", STATUS.UNVERIFIABLE, {
|
|
237219
|
+
reason: auth && auth.reason || NO_TOKEN_INSTRUCTION,
|
|
237220
|
+
endpoint: null,
|
|
237221
|
+
missing_scope: "actions:read / Environments: Read"
|
|
237222
|
+
}));
|
|
237223
|
+
} else if (!environmentRead) {
|
|
237224
|
+
layers.push(layer("environment_protection", STATUS.UNVERIFIABLE, {
|
|
237225
|
+
reason: "environment was not queried",
|
|
237226
|
+
endpoint: null
|
|
237227
|
+
}));
|
|
237228
|
+
} else if (environmentRead.status === 403) {
|
|
237229
|
+
layers.push(layer("environment_protection", STATUS.UNVERIFIABLE, {
|
|
237230
|
+
reason: "GitHub HTTP 403 \u2014 missing scope to read environments (Actions/Environments: Read, or repo admin).",
|
|
237231
|
+
endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
|
|
237232
|
+
missing_scope: "environments:read",
|
|
237233
|
+
http_status: 403
|
|
237234
|
+
}));
|
|
237235
|
+
} else if (environmentRead.status === 404) {
|
|
237236
|
+
layers.push(layer("environment_protection", STATUS.NOT_VERIFIED, {
|
|
237237
|
+
reason: `GitHub Environment "${environmentName}" does not exist (or has no protection)`,
|
|
237238
|
+
endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
|
|
237239
|
+
http_status: 404
|
|
237240
|
+
}));
|
|
237241
|
+
} else if (environmentRead.status >= 400 || environmentRead.status === 0) {
|
|
237242
|
+
layers.push(layer("environment_protection", STATUS.UNVERIFIABLE, {
|
|
237243
|
+
reason: `GitHub HTTP ${environmentRead.status || 0} reading environment "${environmentName}"`,
|
|
237244
|
+
endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
|
|
237245
|
+
http_status: environmentRead.status
|
|
237246
|
+
}));
|
|
237247
|
+
} else {
|
|
237248
|
+
const ok = environmentProtected(environmentRead.body);
|
|
237249
|
+
layers.push(layer("environment_protection", ok ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
237250
|
+
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`,
|
|
237251
|
+
endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
|
|
237252
|
+
environment: environmentName
|
|
237253
|
+
}));
|
|
237254
|
+
}
|
|
237255
|
+
const cg = localFiles.contractGateFiles || [];
|
|
237256
|
+
layers.push(layer("workflow_contract_gate", cg.length ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
237257
|
+
reason: cg.length ? `workflow references ${GATE_ACTION} (${cg.join(", ")})` : `no .github/workflows file references ${GATE_ACTION}`,
|
|
237258
|
+
endpoint: "local:.github/workflows",
|
|
237259
|
+
files: cg
|
|
237260
|
+
}));
|
|
237261
|
+
const dg = localFiles.deployGateFiles || [];
|
|
237262
|
+
layers.push(layer("workflow_deploy_gate", dg.length ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
237263
|
+
reason: dg.length ? `workflow runs coderifts deploy-gate (${dg.join(", ")})` : "no .github/workflows file runs coderifts deploy-gate",
|
|
237264
|
+
endpoint: "local:.github/workflows",
|
|
237265
|
+
files: dg
|
|
237266
|
+
}));
|
|
237267
|
+
const byId = Object.fromEntries(layers.map((l) => [l.id, l]));
|
|
237268
|
+
const inescapable = LAYER_IDS.every((id) => byId[id] && byId[id].status === STATUS.VERIFIED);
|
|
237269
|
+
return {
|
|
237270
|
+
layers,
|
|
237271
|
+
inescapable_deploy: inescapable,
|
|
237272
|
+
claim: {
|
|
237273
|
+
inescapable_deploy: inescapable,
|
|
237274
|
+
basis: inescapable ? "all six layers VERIFIED from queried provider evidence + local workflow files" : "inescapable_deploy stays false until every layer is VERIFIED"
|
|
237275
|
+
}
|
|
237276
|
+
};
|
|
237277
|
+
}
|
|
237278
|
+
function scanLocalWorkflows(cwd, deps = {}) {
|
|
237279
|
+
const exists = deps.exists || fs.existsSync.bind(fs);
|
|
237280
|
+
const readdir = deps.readdir || ((p) => fs.readdirSync(p));
|
|
237281
|
+
const readFile = deps.readFile || ((p) => fs.readFileSync(p, "utf8"));
|
|
237282
|
+
const dir = path.join(cwd, ".github", "workflows");
|
|
237283
|
+
const contractGateFiles = [];
|
|
237284
|
+
const deployGateFiles = [];
|
|
237285
|
+
if (!exists(dir)) return { contractGateFiles, deployGateFiles };
|
|
237286
|
+
let names = [];
|
|
237287
|
+
try {
|
|
237288
|
+
names = readdir(dir);
|
|
237289
|
+
} catch {
|
|
237290
|
+
return { contractGateFiles, deployGateFiles };
|
|
237291
|
+
}
|
|
237292
|
+
for (const name of names) {
|
|
237293
|
+
if (!/\.ya?ml$/i.test(name)) continue;
|
|
237294
|
+
const rel = `.github/workflows/${name}`;
|
|
237295
|
+
let body = "";
|
|
237296
|
+
try {
|
|
237297
|
+
body = String(readFile(path.join(dir, name)));
|
|
237298
|
+
} catch {
|
|
237299
|
+
continue;
|
|
237300
|
+
}
|
|
237301
|
+
if (body.includes(GATE_ACTION)) contractGateFiles.push(rel);
|
|
237302
|
+
if (/\bcoderifts\s+deploy-gate\b/.test(body)) deployGateFiles.push(rel);
|
|
237303
|
+
}
|
|
237304
|
+
return { contractGateFiles, deployGateFiles };
|
|
237305
|
+
}
|
|
237306
|
+
async function queryGitHubEnforcement({
|
|
237307
|
+
owner,
|
|
237308
|
+
repo,
|
|
237309
|
+
branch,
|
|
237310
|
+
environmentName,
|
|
237311
|
+
client
|
|
237312
|
+
} = {}) {
|
|
237313
|
+
const protectionPath = branch ? `repos/${owner}/${repo}/branches/${encodeURIComponent(branch)}/protection` : null;
|
|
237314
|
+
const envPath = environmentName ? `repos/${owner}/${repo}/environments/${encodeURIComponent(environmentName)}` : null;
|
|
237315
|
+
let resolvedBranch = branch || null;
|
|
237316
|
+
let repoRead = null;
|
|
237317
|
+
if (!resolvedBranch && client) {
|
|
237318
|
+
repoRead = await client.get(`repos/${owner}/${repo}`);
|
|
237319
|
+
if (repoRead && repoRead.status === 200 && repoRead.body && repoRead.body.default_branch) {
|
|
237320
|
+
resolvedBranch = String(repoRead.body.default_branch);
|
|
237321
|
+
}
|
|
237322
|
+
}
|
|
237323
|
+
const protPath = resolvedBranch ? `repos/${owner}/${repo}/branches/${encodeURIComponent(resolvedBranch)}/protection` : protectionPath;
|
|
237324
|
+
const protectionRead = client && protPath ? await client.get(protPath) : null;
|
|
237325
|
+
const environmentRead = client && envPath ? await client.get(envPath) : null;
|
|
237326
|
+
return {
|
|
237327
|
+
branch: resolvedBranch,
|
|
237328
|
+
repoRead,
|
|
237329
|
+
protectionRead,
|
|
237330
|
+
environmentRead
|
|
237331
|
+
};
|
|
237332
|
+
}
|
|
237333
|
+
module2.exports = {
|
|
237334
|
+
EVIDENCE_SPEC,
|
|
237335
|
+
APP_INSTALLATION,
|
|
237336
|
+
NO_TOKEN_INSTRUCTION,
|
|
237337
|
+
LAYER_IDS,
|
|
237338
|
+
STATUS,
|
|
237339
|
+
CHECK_NAME,
|
|
237340
|
+
GATE_ACTION,
|
|
237341
|
+
resolveGitHubAuth,
|
|
237342
|
+
redactSecrets,
|
|
237343
|
+
createGitHubHttpClient,
|
|
237344
|
+
evaluateLayers,
|
|
237345
|
+
scanLocalWorkflows,
|
|
237346
|
+
queryGitHubEnforcement,
|
|
237347
|
+
enforceAdminsEnabled,
|
|
237348
|
+
strictEnabled,
|
|
237349
|
+
environmentProtected
|
|
237350
|
+
};
|
|
237351
|
+
}
|
|
237352
|
+
});
|
|
237353
|
+
|
|
237354
|
+
// src/commands/enforce-check.js
|
|
237355
|
+
var require_enforce_check = __commonJS({
|
|
237356
|
+
"src/commands/enforce-check.js"(exports2, module2) {
|
|
237357
|
+
"use strict";
|
|
237358
|
+
var chalk = require_source();
|
|
237359
|
+
var {
|
|
237360
|
+
EVIDENCE_SPEC,
|
|
237361
|
+
APP_INSTALLATION,
|
|
237362
|
+
NO_TOKEN_INSTRUCTION,
|
|
237363
|
+
resolveGitHubAuth,
|
|
237364
|
+
createGitHubHttpClient,
|
|
237365
|
+
evaluateLayers,
|
|
237366
|
+
scanLocalWorkflows,
|
|
237367
|
+
queryGitHubEnforcement
|
|
237368
|
+
} = require_github_enforcement();
|
|
237369
|
+
var { isValidRepo } = require_status();
|
|
237370
|
+
var { resolveOwnerRepo } = require_setup_required_check();
|
|
237371
|
+
if (process.env.NO_COLOR) chalk.level = 0;
|
|
237372
|
+
var USAGE = [
|
|
237373
|
+
"Usage: coderifts enforce --check --repo owner/repo [--env <github-environment>] [--branch <name>] [--json]",
|
|
237374
|
+
"",
|
|
237375
|
+
"Query the provider (GitHub) and local workflow files. Report, per layer:",
|
|
237376
|
+
" VERIFIED / NOT_VERIFIED / UNVERIFIABLE(reason).",
|
|
237377
|
+
"inescapable_deploy is true only when every layer is VERIFIED.",
|
|
237378
|
+
"",
|
|
237379
|
+
"Auth: GITHUB_TOKEN (or GH_TOKEN). Never prints token values.",
|
|
237380
|
+
"The CodeRifts GitHub App installation cannot query branch protection",
|
|
237381
|
+
"(missing administration:read)."
|
|
237382
|
+
].join("\n");
|
|
237383
|
+
function statusColor(status) {
|
|
237384
|
+
if (status === "VERIFIED") return chalk.green(status);
|
|
237385
|
+
if (status === "NOT_VERIFIED") return chalk.yellow(status);
|
|
237386
|
+
return chalk.red(status);
|
|
237387
|
+
}
|
|
237388
|
+
function renderHuman(report) {
|
|
237389
|
+
const lines = [];
|
|
237390
|
+
lines.push(chalk.bold("CodeRifts enforce --check"));
|
|
237391
|
+
lines.push(` repo: ${report.repo}`);
|
|
237392
|
+
lines.push(` branch: ${report.branch || "(unknown)"}`);
|
|
237393
|
+
if (report.environment) lines.push(` env: ${report.environment}`);
|
|
237394
|
+
lines.push(` queried: ${report.queried_at}`);
|
|
237395
|
+
lines.push(` auth: ${report.auth.source} (value not printed)`);
|
|
237396
|
+
lines.push(chalk.dim(` App installation: cannot query protection (${report.auth.app_installation.missing_scope})`));
|
|
237397
|
+
lines.push("");
|
|
237398
|
+
lines.push(chalk.bold(" Layer Status"));
|
|
237399
|
+
for (const l of report.layers) {
|
|
237400
|
+
const id = String(l.id).padEnd(34, " ");
|
|
237401
|
+
lines.push(` ${id}${statusColor(l.status)}`);
|
|
237402
|
+
if (l.status !== "VERIFIED" && l.reason) {
|
|
237403
|
+
lines.push(chalk.dim(` WHY ${l.reason}`));
|
|
237404
|
+
}
|
|
237405
|
+
if (l.endpoint) lines.push(chalk.dim(` via ${l.endpoint}`));
|
|
237406
|
+
}
|
|
237407
|
+
lines.push("");
|
|
237408
|
+
const claim = report.claim && report.claim.inescapable_deploy === true;
|
|
237409
|
+
lines.push(` inescapable_deploy: ${claim ? chalk.green("true") : chalk.yellow("false")}`);
|
|
237410
|
+
lines.push(chalk.dim(` ${report.claim.basis}`));
|
|
237411
|
+
lines.push("");
|
|
237412
|
+
lines.push(chalk.dim(" This is queried evidence, not a host claim."));
|
|
237413
|
+
lines.push(chalk.dim(" --enforce / CODERIFTS_DEPLOY_NO_BYPASS are host claims and do not set this field."));
|
|
237414
|
+
if (!claim) {
|
|
237415
|
+
lines.push("");
|
|
237416
|
+
lines.push(chalk.bold(" ACTION"));
|
|
237417
|
+
const missing = report.layers.filter((l) => l.status !== "VERIFIED");
|
|
237418
|
+
for (const l of missing) {
|
|
237419
|
+
if (l.id === "required_check") {
|
|
237420
|
+
lines.push(chalk.dim(' require "CodeRifts / contract-gate" on default-branch protection'));
|
|
237421
|
+
lines.push(chalk.dim(" (coderifts setup-required-check --apply)"));
|
|
237422
|
+
} else if (l.id === "enforce_admins") {
|
|
237423
|
+
lines.push(chalk.dim(" set enforce_admins: true so administrators cannot bypass"));
|
|
237424
|
+
lines.push(chalk.dim(" (coderifts setup-required-check --enforce-admins --apply)"));
|
|
237425
|
+
} else if (l.id === "strict_status_checks") {
|
|
237426
|
+
lines.push(chalk.dim(" set required_status_checks.strict: true"));
|
|
237427
|
+
} else if (l.id === "environment_protection") {
|
|
237428
|
+
lines.push(chalk.dim(" add a GitHub Environment with protection rules, pass --env <name>"));
|
|
237429
|
+
} else if (l.id === "workflow_contract_gate") {
|
|
237430
|
+
lines.push(chalk.dim(" add .github/workflows/coderifts.yml using coderifts/contract-gate@v0"));
|
|
237431
|
+
lines.push(chalk.dim(" (coderifts init --agents)"));
|
|
237432
|
+
} else if (l.id === "workflow_deploy_gate") {
|
|
237433
|
+
lines.push(chalk.dim(" add a CD step: coderifts deploy-gate --env <env> --artifact <id> --receipt <file>"));
|
|
237434
|
+
}
|
|
237435
|
+
}
|
|
237436
|
+
}
|
|
237437
|
+
return lines.join("\n");
|
|
237438
|
+
}
|
|
237439
|
+
async function runEnforceCheck(options = {}, deps = {}) {
|
|
237440
|
+
const log = deps.log || console.log.bind(console);
|
|
237441
|
+
const errLog = deps.errLog || console.error.bind(console);
|
|
237442
|
+
const cwd = deps.cwd || options.cwd || process.cwd();
|
|
237443
|
+
const now = deps.now || (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
237444
|
+
const env = deps.env || process.env;
|
|
237445
|
+
const repoArg = options.repo;
|
|
237446
|
+
let ownerRepo = null;
|
|
237447
|
+
if (repoArg && isValidRepo(String(repoArg).trim())) {
|
|
237448
|
+
const [owner, repo] = String(repoArg).trim().split("/");
|
|
237449
|
+
ownerRepo = { owner, repo };
|
|
237450
|
+
} else if (!repoArg) {
|
|
237451
|
+
try {
|
|
237452
|
+
ownerRepo = resolveOwnerRepo(cwd, deps.gitImpl);
|
|
237453
|
+
} catch {
|
|
237454
|
+
ownerRepo = null;
|
|
237455
|
+
}
|
|
237456
|
+
}
|
|
237457
|
+
if (!ownerRepo) {
|
|
237458
|
+
errLog(chalk.red("Error: missing repo (pass --repo owner/repo)"));
|
|
237459
|
+
errLog(USAGE);
|
|
237460
|
+
return { exitCode: 1, error: "missing_repo" };
|
|
237461
|
+
}
|
|
237462
|
+
const providerName = String(options.provider || "github").toLowerCase();
|
|
237463
|
+
const queriedAt = now();
|
|
237464
|
+
const environmentName = options.env ? String(options.env).trim() : null;
|
|
237465
|
+
const local = (deps.scanLocalWorkflows || scanLocalWorkflows)(cwd, deps.fs || {});
|
|
237466
|
+
const authPublic = {
|
|
237467
|
+
source: "none",
|
|
237468
|
+
app_installation: { ...APP_INSTALLATION }
|
|
237469
|
+
};
|
|
237470
|
+
if (providerName !== "github") {
|
|
237471
|
+
const reason = `no ${providerName} adapter \u2014 GitHub is the only implemented provider; refusing to stub results`;
|
|
237472
|
+
const evaluated2 = evaluateLayers({
|
|
237473
|
+
protectionRead: null,
|
|
237474
|
+
environmentRead: null,
|
|
237475
|
+
environmentName,
|
|
237476
|
+
local,
|
|
237477
|
+
auth: { ok: false, source: "none", reason }
|
|
237478
|
+
});
|
|
237479
|
+
const report2 = {
|
|
237480
|
+
command: "enforce --check",
|
|
237481
|
+
spec: EVIDENCE_SPEC,
|
|
237482
|
+
queried_at: queriedAt,
|
|
237483
|
+
repo: `${ownerRepo.owner}/${ownerRepo.repo}`,
|
|
237484
|
+
branch: options.branch || null,
|
|
237485
|
+
environment: environmentName,
|
|
237486
|
+
provider: providerName,
|
|
237487
|
+
auth: { ...authPublic, reason },
|
|
237488
|
+
...evaluated2
|
|
237489
|
+
};
|
|
237490
|
+
if (options.json) log(JSON.stringify(report2, null, 2));
|
|
237491
|
+
else log(renderHuman(report2));
|
|
237492
|
+
return { exitCode: 0, report: report2 };
|
|
237493
|
+
}
|
|
237494
|
+
const auth = (deps.resolveAuth || resolveGitHubAuth)(env);
|
|
237495
|
+
authPublic.source = auth.source;
|
|
237496
|
+
if (auth.reason) authPublic.reason = auth.reason;
|
|
237497
|
+
let branch = options.branch ? String(options.branch).trim() : null;
|
|
237498
|
+
let protectionRead = null;
|
|
237499
|
+
let environmentRead = null;
|
|
237500
|
+
if (auth.ok) {
|
|
237501
|
+
const client = deps.githubClient || createGitHubHttpClient({
|
|
237502
|
+
token: auth.token,
|
|
237503
|
+
fetchImpl: deps.fetchImpl
|
|
237504
|
+
});
|
|
237505
|
+
const queried = await queryGitHubEnforcement({
|
|
237506
|
+
owner: ownerRepo.owner,
|
|
237507
|
+
repo: ownerRepo.repo,
|
|
237508
|
+
branch,
|
|
237509
|
+
environmentName,
|
|
237510
|
+
client
|
|
237511
|
+
});
|
|
237512
|
+
branch = queried.branch || branch;
|
|
237513
|
+
protectionRead = queried.protectionRead;
|
|
237514
|
+
environmentRead = queried.environmentRead;
|
|
237515
|
+
}
|
|
237516
|
+
const evaluated = evaluateLayers({
|
|
237517
|
+
protectionRead,
|
|
237518
|
+
environmentRead,
|
|
237519
|
+
environmentName,
|
|
237520
|
+
local,
|
|
237521
|
+
auth
|
|
237522
|
+
});
|
|
237523
|
+
const report = {
|
|
237524
|
+
command: "enforce --check",
|
|
237525
|
+
spec: EVIDENCE_SPEC,
|
|
237526
|
+
queried_at: queriedAt,
|
|
237527
|
+
repo: `${ownerRepo.owner}/${ownerRepo.repo}`,
|
|
237528
|
+
branch,
|
|
237529
|
+
environment: environmentName,
|
|
237530
|
+
provider: "github",
|
|
237531
|
+
auth: authPublic,
|
|
237532
|
+
...evaluated
|
|
237533
|
+
};
|
|
237534
|
+
if (options.json) log(JSON.stringify(report, null, 2));
|
|
237535
|
+
else log(renderHuman(report));
|
|
237536
|
+
return { exitCode: 0, report };
|
|
237537
|
+
}
|
|
237538
|
+
module2.exports = {
|
|
237539
|
+
runEnforceCheck,
|
|
237540
|
+
renderHuman,
|
|
237541
|
+
USAGE,
|
|
237542
|
+
NO_TOKEN_INSTRUCTION
|
|
237543
|
+
};
|
|
237544
|
+
}
|
|
237545
|
+
});
|
|
237546
|
+
|
|
237002
237547
|
// src/commands/enforce.js
|
|
237003
237548
|
var require_enforce = __commonJS({
|
|
237004
237549
|
"src/commands/enforce.js"(exports2, module2) {
|
|
@@ -237021,14 +237566,19 @@ var require_enforce = __commonJS({
|
|
|
237021
237566
|
var USAGE = [
|
|
237022
237567
|
"Usage: coderifts enforce --repo owner/repo [--apply]",
|
|
237023
237568
|
" or: coderifts enforce owner/repo [--apply]",
|
|
237569
|
+
" or: coderifts enforce --check --repo owner/repo [--env <name>] [--json]",
|
|
237024
237570
|
"",
|
|
237025
237571
|
"Cross-layer orchestrator: reads enforcement-status, then closes gaps by calling existing",
|
|
237026
237572
|
"setup commands (setup-required-check, hook install, deploy-gate guidance).",
|
|
237027
237573
|
"",
|
|
237574
|
+
"--check: query GitHub + local workflows and report VERIFIED / NOT_VERIFIED /",
|
|
237575
|
+
"UNVERIFIABLE per layer. inescapable_deploy is true only when every layer is VERIFIED.",
|
|
237576
|
+
"Does not use the CodeRifts API key. Auth: GITHUB_TOKEN. Read-only.",
|
|
237577
|
+
"",
|
|
237028
237578
|
"DRY-RUN BY DEFAULT \u2014 without --apply, prints what WOULD run and mutates NOTHING.",
|
|
237029
237579
|
"With --apply, threads apply into each underlying command (their own safety still applies).",
|
|
237030
237580
|
"",
|
|
237031
|
-
"Requires a cloud API key for the status read (coderifts login / CODERIFTS_API_KEY).",
|
|
237581
|
+
"Requires a cloud API key for the (non --check) status read (coderifts login / CODERIFTS_API_KEY).",
|
|
237032
237582
|
"Merge apply uses your local `gh` credentials, not the CodeRifts API key."
|
|
237033
237583
|
].join("\n");
|
|
237034
237584
|
var AGENT_GUARD_GUIDANCE = [
|
|
@@ -237039,8 +237589,10 @@ var require_enforce = __commonJS({
|
|
|
237039
237589
|
].join("\n");
|
|
237040
237590
|
var DEPLOY_GUIDANCE = [
|
|
237041
237591
|
"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
|
|
237592
|
+
"Add a CD step that runs: coderifts deploy-gate --env <env> --artifact <id> --receipt <file>",
|
|
237593
|
+
" (fail-closed by default. --enforce is a host claim, not inescapable_deploy.",
|
|
237594
|
+
" Prove pipeline protection with: coderifts enforce --check --repo owner/repo --env <env>.",
|
|
237595
|
+
" CODERIFTS_DEPLOY_ADVISORY=1 to soften the exit.)",
|
|
237044
237596
|
"Also set policy.require_source_binding: true in .coderifts.yml for the deploy declaration leg."
|
|
237045
237597
|
].join("\n");
|
|
237046
237598
|
var CONTENT_GUIDANCE = [
|
|
@@ -237067,6 +237619,15 @@ var require_enforce = __commonJS({
|
|
|
237067
237619
|
}
|
|
237068
237620
|
}
|
|
237069
237621
|
async function runEnforce(options = {}, deps = {}) {
|
|
237622
|
+
if (options.check === true) {
|
|
237623
|
+
if (options.apply === true) {
|
|
237624
|
+
const errLog2 = deps.errLog || console.error.bind(console);
|
|
237625
|
+
errLog2(chalk.red("Error: --check is read-only evidence and cannot be combined with --apply"));
|
|
237626
|
+
return { exitCode: 1, error: "check_apply_conflict", outcomes: [] };
|
|
237627
|
+
}
|
|
237628
|
+
const { runEnforceCheck } = require_enforce_check();
|
|
237629
|
+
return runEnforceCheck(options, deps);
|
|
237630
|
+
}
|
|
237070
237631
|
const apply = options.apply === true;
|
|
237071
237632
|
const getKey = deps.getApiKey || getApiKey;
|
|
237072
237633
|
const fetchStatus = deps.cloudGetEnforcementStatus || cloudGetEnforcementStatus;
|
|
@@ -240058,12 +240619,13 @@ program.command("status [repo]").description("Show cross-layer enforcement statu
|
|
|
240058
240619
|
process.exitCode = result.exitCode;
|
|
240059
240620
|
}
|
|
240060
240621
|
});
|
|
240061
|
-
program.command("enforce [repo]").description("Close enforcement gaps
|
|
240622
|
+
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
240623
|
const { runEnforce } = require_enforce();
|
|
240063
240624
|
const result = await runEnforce({
|
|
240064
240625
|
...options,
|
|
240065
240626
|
repo: options.repo || repoPositional || null,
|
|
240066
|
-
apply: !!options.apply
|
|
240627
|
+
apply: !!options.apply,
|
|
240628
|
+
check: !!options.check
|
|
240067
240629
|
});
|
|
240068
240630
|
if (result && typeof result.exitCode === "number") {
|
|
240069
240631
|
process.exitCode = result.exitCode;
|
package/package.json
CHANGED