coderifts 4.8.0 → 4.10.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 +39 -0
- package/README.md +13 -8
- package/bin/coderifts.js +2 -0
- package/dist/cli.js +452 -40
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,44 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.10.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **`coderifts init --agents --strict`** writes the STRICT contract-gate
|
|
7
|
+
workflow: `require-verified-monitoring: 'true'` (plus attestation /
|
|
8
|
+
keyring inputs). The default template is unchanged (monitoring stays
|
|
9
|
+
commented / false).
|
|
10
|
+
|
|
11
|
+
## 4.9.0
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **P0 / ID621 — `enforce --check` no longer reports `inescapable_deploy: true`
|
|
15
|
+
for an unenforced configuration.** External audit 2026-08-24 reproduced two
|
|
16
|
+
holes on the published CLI: (a) the required check matched by **name only**
|
|
17
|
+
(`required_status_checks.checks[]` with no `app_id`), so any repository
|
|
18
|
+
writer could satisfy it with a same-named success status; (b) the two local
|
|
19
|
+
workflow checks were **text searches**, so `# uses: coderifts/contract-gate@v0`
|
|
20
|
+
in a comment counted as VERIFIED. Both now fail closed.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- **`--json` spec `provider-enforcement-evidence.v2`.** The meaning of
|
|
24
|
+
`VERIFIED` changed. A layer whose evidence cannot be bound to an issuer
|
|
25
|
+
(CodeRifts GitHub App id, public `GET /apps/coderifts`) or to an **active**
|
|
26
|
+
workflow job is `NOT_VERIFIED` or `UNVERIFIABLE` — never `VERIFIED`.
|
|
27
|
+
`inescapable_deploy` is true only when every layer is VERIFIED under these
|
|
28
|
+
rules.
|
|
29
|
+
- **Issuer binding:** `required_check` is VERIFIED only when `checks[]`
|
|
30
|
+
carries our App's numeric `app_id`. Legacy `contexts: string[]` is
|
|
31
|
+
`legacy_contexts_no_issuer_binding`. Name-only `checks[]` is
|
|
32
|
+
`no_issuer_binding`. A different `app_id` is `issuer_app_id_mismatch`.
|
|
33
|
+
- **YAML AST, not grep:** workflow files are parsed with `js-yaml` (already a
|
|
34
|
+
CLI dependency — no new package). VERIFIED requires a job that is not
|
|
35
|
+
`if: false` and a step whose `uses:` is `coderifts/contract-gate` at a
|
|
36
|
+
pinned major, or a `run:` that actually invokes `coderifts deploy-gate`.
|
|
37
|
+
Comments, string literals in other keys, and commented-out steps fail.
|
|
38
|
+
- **Environment correlation:** `environment_protection` is VERIFIED only when
|
|
39
|
+
`--env` names a protected GitHub Environment **and** an active deploy-gate
|
|
40
|
+
job sets that same `environment:` key (`environment_mismatch` otherwise).
|
|
41
|
+
|
|
3
42
|
## 4.8.0
|
|
4
43
|
|
|
5
44
|
### Fixed
|
package/README.md
CHANGED
|
@@ -75,6 +75,7 @@ coderifts init --agents --hosts=cursor # Cursor only
|
|
|
75
75
|
coderifts init --agents --hosts=copilot # GitHub Copilot / VS Code only
|
|
76
76
|
coderifts init --agents --no-hook # skip host hook install
|
|
77
77
|
coderifts init --agents --no-workflow # skip .github/workflows/coderifts.yml
|
|
78
|
+
coderifts init --agents --strict # STRICT: require-verified-monitoring: true
|
|
78
79
|
coderifts init --agents --check # which of the four pieces are present
|
|
79
80
|
```
|
|
80
81
|
|
|
@@ -83,7 +84,7 @@ coderifts init --agents --check # which of the four pieces are present
|
|
|
83
84
|
| **MCP config** | `.mcp.json` (Claude Code), `.cursor/mcp.json` (Cursor), `.vscode/mcp.json` (Copilot / VS Code) | Three shapes, never normalised into one: Claude `mcpServers` + `type: "http"` + `url`; Cursor `mcpServers` + `url` only (no `type`); Copilot **`servers`** (not `mcpServers`) + `type: "http"` + `url`. Never `httpUrl` (that is Gemini, extension-level — this command does not write it). |
|
|
84
85
|
| **Agent rules** | generated host files via `agent-setup`'s writer | `AGENTS.md` is always written. Claude also gets `CLAUDE.md`; Cursor also gets `.cursor/rules/coderifts.mdc`; Copilot also gets `.github/copilot-instructions.md`. `--hosts=all` adds LangGraph and OpenAI instructions. (`coderifts init agent` is a different command — it writes a `.coderifts.yml` template.) |
|
|
85
86
|
| **Host hook** | `.claude/settings.json`, `.cursor/hooks.json` | Reuses `hook install --claude` / `--cursor` (JSON-merge, backup, idempotent). Copilot shares Claude's `.claude/settings.json` — VS Code reads it by default. |
|
|
86
|
-
| **CI workflow** | `.github/workflows/coderifts.yml` | `coderifts/contract-gate@v0`. `require-verified-monitoring`
|
|
87
|
+
| **CI workflow** | `.github/workflows/coderifts.yml` | `coderifts/contract-gate@v0`. Default: `require-verified-monitoring` commented (honest default: **false**). `--strict`: the same file with `require-verified-monitoring: 'true'` |
|
|
87
88
|
|
|
88
89
|
**Next manual step:** add `CODERIFTS_API_KEY` to the repository secrets. The
|
|
89
90
|
CLI never writes a key anywhere.
|
|
@@ -437,12 +438,14 @@ Provider-native pipeline-protection evidence. This is **not** a host claim:
|
|
|
437
438
|
|
|
438
439
|
| Layer | VERIFIED when |
|
|
439
440
|
|-------|----------------|
|
|
440
|
-
| `required_check` | Default-branch protection
|
|
441
|
+
| `required_check` | Default-branch protection `required_status_checks.checks[]` lists `CodeRifts / contract-gate` **bound to the CodeRifts GitHub App `app_id`** (public metadata: `GET https://api.github.com/apps/coderifts`). Name-only `checks[]` (`app_id` null) is `no_issuer_binding`. The legacy `contexts` array has no issuer field → `legacy_contexts_no_issuer_binding`. Both are **NOT_VERIFIED** (ID621). |
|
|
441
442
|
| `enforce_admins` | `enforce_admins.enabled` is **true** (admins cannot bypass). `false` = NOT_VERIFIED (admin bypass open) |
|
|
442
443
|
| `strict_status_checks` | `required_status_checks.strict` is true |
|
|
443
|
-
| `environment_protection` | `--env` names a GitHub Environment with protection rules
|
|
444
|
-
| `workflow_contract_gate` |
|
|
445
|
-
| `workflow_deploy_gate` |
|
|
444
|
+
| `environment_protection` | `--env` names a GitHub Environment with protection rules **and** an active deploy-gate job sets that same `environment:` (mismatch → `environment_mismatch`) |
|
|
445
|
+
| `workflow_contract_gate` | an **active** job (not `if: false`) has a step whose `uses:` is `coderifts/contract-gate` at a pinned major. Comments, string literals, and commented-out steps are **NOT_VERIFIED** |
|
|
446
|
+
| `workflow_deploy_gate` | an **active** job step **invokes** `coderifts deploy-gate` (not `echo` of the string; not a comment) |
|
|
447
|
+
|
|
448
|
+
**Invariant:** any layer whose evidence cannot be bound to an issuer (our GitHub App id) or to an active job is `NOT_VERIFIED` or `UNVERIFIABLE` — never `VERIFIED`. `inescapable_deploy` remains true only when every layer is `VERIFIED` under that rule.
|
|
446
449
|
|
|
447
450
|
Statuses: `VERIFIED` / `NOT_VERIFIED` / `UNVERIFIABLE(reason)`. Auth:
|
|
448
451
|
`GITHUB_TOKEN` (never printed). **No token** → API layers `UNVERIFIABLE` with
|
|
@@ -451,9 +454,11 @@ CodeRifts GitHub App **cannot** query branch protection (permissions are
|
|
|
451
454
|
contents / pull requests / checks — not `administration:read`).
|
|
452
455
|
|
|
453
456
|
`--json` is the quotable evidence document (`spec`:
|
|
454
|
-
`provider-enforcement-evidence.
|
|
455
|
-
|
|
456
|
-
|
|
457
|
+
`provider-enforcement-evidence.v2` — VERIFIED now requires issuer binding and
|
|
458
|
+
an active YAML-AST job; v1 treated name-only checks and text search as
|
|
459
|
+
VERIFIED): each layer's status, `reason_code`, the API endpoint consulted,
|
|
460
|
+
`queried_at`. Exit **0** when a report was produced (evidence, not a gate).
|
|
461
|
+
GitLab/Bitbucket: no adapter — `UNVERIFIABLE`, never stubbed.
|
|
457
462
|
|
|
458
463
|
### Agent host files (`coderifts agent-setup`)
|
|
459
464
|
|
package/bin/coderifts.js
CHANGED
|
@@ -95,6 +95,7 @@ program
|
|
|
95
95
|
.option('--hosts <list>', 'With --agents: claude, cursor, copilot, or all (default: all)', 'all')
|
|
96
96
|
.option('--no-hook', 'With --agents: skip host hook install')
|
|
97
97
|
.option('--no-workflow', 'With --agents: skip writing .github/workflows/coderifts.yml')
|
|
98
|
+
.option('--strict', 'With --agents: STRICT workflow (require-verified-monitoring: true)')
|
|
98
99
|
.option('--dry-run', 'With --agents: print the plan without writing')
|
|
99
100
|
.option('--check', 'With --agents: report which of the four pieces are present (no write)')
|
|
100
101
|
.option('--out <dir>', 'With --agents: target directory (default: cwd)')
|
|
@@ -118,6 +119,7 @@ program
|
|
|
118
119
|
dryRun: !!options.dryRun,
|
|
119
120
|
check: !!options.check,
|
|
120
121
|
out: options.out,
|
|
122
|
+
strict: !!options.strict,
|
|
121
123
|
});
|
|
122
124
|
if (result && typeof result.exitCode === 'number') {
|
|
123
125
|
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.10.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",
|
|
@@ -200889,9 +200889,20 @@ var require_decision_result_v1_producer = __commonJS({
|
|
|
200889
200889
|
source: {
|
|
200890
200890
|
type: "string",
|
|
200891
200891
|
enum: [
|
|
200892
|
-
"github_compare"
|
|
200892
|
+
"github_compare",
|
|
200893
|
+
"gitlab_compare",
|
|
200894
|
+
"bitbucket_compare"
|
|
200893
200895
|
],
|
|
200894
|
-
description: "How the server listed the change-set. github_compare = GitHub Compare API via the App installation."
|
|
200896
|
+
description: "How the server listed the change-set. github_compare = GitHub Compare API via the App installation. gitlab_compare / bitbucket_compare = platform Compare via a caller-supplied short-lived SCM token (never stored)."
|
|
200897
|
+
},
|
|
200898
|
+
platform: {
|
|
200899
|
+
type: "string",
|
|
200900
|
+
enum: [
|
|
200901
|
+
"github",
|
|
200902
|
+
"gitlab",
|
|
200903
|
+
"bitbucket"
|
|
200904
|
+
],
|
|
200905
|
+
description: "Additive P1-6. SCM platform that produced this SERVER_DERIVED set. Taken from the proven binding / context.platform \u2014 never guessed from the repository string. Absent on envelopes minted before this field existed."
|
|
200895
200906
|
},
|
|
200896
200907
|
base_sha: {
|
|
200897
200908
|
type: "string",
|
|
@@ -201799,6 +201810,9 @@ var require_change_set_completeness = __commonJS({
|
|
|
201799
201810
|
if (v == null) return "";
|
|
201800
201811
|
return typeof v === "string" ? v : JSON.stringify(v);
|
|
201801
201812
|
}
|
|
201813
|
+
function afterContentHash(content) {
|
|
201814
|
+
return sha256hexLocal(specStr(content));
|
|
201815
|
+
}
|
|
201802
201816
|
function artifactPath(artifact) {
|
|
201803
201817
|
if (!artifact || typeof artifact !== "object") return "";
|
|
201804
201818
|
if (typeof artifact.path === "string" && artifact.path.trim()) return artifact.path.trim();
|
|
@@ -202097,7 +202111,8 @@ var require_change_set_completeness = __commonJS({
|
|
|
202097
202111
|
contract_selector_hash,
|
|
202098
202112
|
completeness_count: normalizedLeaves.length,
|
|
202099
202113
|
submitted_set_digest,
|
|
202100
|
-
expected_channel: "webhook_full_file_list"
|
|
202114
|
+
expected_channel: "webhook_full_file_list",
|
|
202115
|
+
leaves: normalizedLeaves
|
|
202101
202116
|
};
|
|
202102
202117
|
}
|
|
202103
202118
|
function assertCompletenessRequirement(block, requireLevel) {
|
|
@@ -202181,9 +202196,45 @@ var require_change_set_completeness = __commonJS({
|
|
|
202181
202196
|
contract_selector_hash: computeContractSelectorHash(DEFAULT_CONTRACT_SELECTOR),
|
|
202182
202197
|
completeness_count: leaves.length,
|
|
202183
202198
|
submitted_set_digest,
|
|
202184
|
-
expected_channel: null
|
|
202199
|
+
expected_channel: null,
|
|
202200
|
+
leaves
|
|
202185
202201
|
};
|
|
202186
202202
|
}
|
|
202203
|
+
function authorizedPathsFromArtifacts(artifacts) {
|
|
202204
|
+
const list = Array.isArray(artifacts) ? artifacts : [];
|
|
202205
|
+
const seen = /* @__PURE__ */ new Set();
|
|
202206
|
+
const rows = [];
|
|
202207
|
+
for (const a of list) {
|
|
202208
|
+
const path = artifactPath(a);
|
|
202209
|
+
if (!path || seen.has(path)) continue;
|
|
202210
|
+
seen.add(path);
|
|
202211
|
+
rows.push({
|
|
202212
|
+
path,
|
|
202213
|
+
after_hex: sha256hexLocal(specStr(a && a.after)),
|
|
202214
|
+
change_type: deriveChangeType(a && a.before, a && a.after)
|
|
202215
|
+
});
|
|
202216
|
+
}
|
|
202217
|
+
return rows;
|
|
202218
|
+
}
|
|
202219
|
+
function authorizedPathsForPersist(block, artifacts) {
|
|
202220
|
+
const submitted = authorizedPathsFromArtifacts(artifacts);
|
|
202221
|
+
const leaves = block && Array.isArray(block.leaves) ? block.leaves : null;
|
|
202222
|
+
if (!leaves || leaves.length === 0) return submitted;
|
|
202223
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
202224
|
+
for (const L of leaves) {
|
|
202225
|
+
if (!L || typeof L !== "object") continue;
|
|
202226
|
+
const path = String(L.path == null ? "" : L.path).trim();
|
|
202227
|
+
if (!path) continue;
|
|
202228
|
+
const after_hex = String(L.after_hex == null ? "" : L.after_hex).replace(/^sha256:/i, "").toLowerCase();
|
|
202229
|
+
if (!/^[0-9a-f]{64}$/.test(after_hex)) continue;
|
|
202230
|
+
byPath.set(path, {
|
|
202231
|
+
path,
|
|
202232
|
+
after_hex,
|
|
202233
|
+
change_type: normalizeChangeType(L.change_type)
|
|
202234
|
+
});
|
|
202235
|
+
}
|
|
202236
|
+
return submitted.map((row) => byPath.get(row.path) || row);
|
|
202237
|
+
}
|
|
202187
202238
|
function completenessToEnvelopeFields(block) {
|
|
202188
202239
|
if (!block || block.completeness_mode === "UNBOUND") {
|
|
202189
202240
|
return {
|
|
@@ -202230,7 +202281,10 @@ var require_change_set_completeness = __commonJS({
|
|
|
202230
202281
|
assertNoBooleanCompleteness,
|
|
202231
202282
|
completenessRequiredError,
|
|
202232
202283
|
sha256hexLocal,
|
|
202233
|
-
specStr
|
|
202284
|
+
specStr,
|
|
202285
|
+
afterContentHash,
|
|
202286
|
+
authorizedPathsFromArtifacts,
|
|
202287
|
+
authorizedPathsForPersist
|
|
202234
202288
|
};
|
|
202235
202289
|
}
|
|
202236
202290
|
});
|
|
@@ -202795,7 +202849,8 @@ var require_change_set = __commonJS({
|
|
|
202795
202849
|
assertCompletenessRequirement,
|
|
202796
202850
|
completenessToEnvelopeFields,
|
|
202797
202851
|
applyProvenRepoBinding,
|
|
202798
|
-
assertNoBooleanCompleteness
|
|
202852
|
+
assertNoBooleanCompleteness,
|
|
202853
|
+
authorizedPathsForPersist
|
|
202799
202854
|
} = require_change_set_completeness();
|
|
202800
202855
|
var { authorFingerprintBindingEnvelopeFields } = require_fingerprint_binding_gate();
|
|
202801
202856
|
var NUL = "";
|
|
@@ -203522,6 +203577,7 @@ var require_change_set = __commonJS({
|
|
|
203522
203577
|
}
|
|
203523
203578
|
}
|
|
203524
203579
|
const completenessEnvelope = completenessToEnvelopeFields(completenessBlock);
|
|
203580
|
+
const authorizedPaths = preflightMode === "authorize" ? authorizedPathsForPersist(completenessBlock, artifacts) : [];
|
|
203525
203581
|
const fingerprintBindingEnvelope = authorFingerprintBindingEnvelopeFields({
|
|
203526
203582
|
preflightMode
|
|
203527
203583
|
});
|
|
@@ -203583,6 +203639,7 @@ var require_change_set = __commonJS({
|
|
|
203583
203639
|
// body_hash stays byte-identical. Covered by body_hash when set; NOT in fingerprint.
|
|
203584
203640
|
...input && input.derivation_meta && typeof input.derivation_meta === "object" ? { derivation: {
|
|
203585
203641
|
source: String(input.derivation_meta.source || "github_compare"),
|
|
203642
|
+
platform: String(input.derivation_meta.platform || "github"),
|
|
203586
203643
|
base_sha: String(input.derivation_meta.base_sha || ""),
|
|
203587
203644
|
head_sha: String(input.derivation_meta.head_sha || "")
|
|
203588
203645
|
} } : {},
|
|
@@ -203777,6 +203834,12 @@ var require_change_set = __commonJS({
|
|
|
203777
203834
|
}
|
|
203778
203835
|
} catch (_) {
|
|
203779
203836
|
}
|
|
203837
|
+
if (preflightMode === "authorize" && Array.isArray(authorizedPaths) && authorizedPaths.length > 0) {
|
|
203838
|
+
Object.defineProperty(out, "_authorized_paths", {
|
|
203839
|
+
value: Object.freeze(authorizedPaths.map((r) => Object.freeze({ ...r }))),
|
|
203840
|
+
enumerable: false
|
|
203841
|
+
});
|
|
203842
|
+
}
|
|
203780
203843
|
return out;
|
|
203781
203844
|
}
|
|
203782
203845
|
module2.exports = {
|
|
@@ -210159,6 +210222,46 @@ jobs:
|
|
|
210159
210222
|
# monitoring-attestation: \${{ vars.CODERIFTS_MONITORING_ATTESTATION }}
|
|
210160
210223
|
# monitoring-keyring: \${{ github.workspace }}/.coderifts/monitoring-keys.json
|
|
210161
210224
|
`;
|
|
210225
|
+
var STRICT_WORKFLOW_BODY = `# Written by \`coderifts init --agents --strict\`. Re-run is a no-op when this file already exists.
|
|
210226
|
+
#
|
|
210227
|
+
# STRICT template: require-verified-monitoring is ON.
|
|
210228
|
+
# This workflow POSTS the Check Run named exactly:
|
|
210229
|
+
# CodeRifts / contract-gate
|
|
210230
|
+
# It does NOT enable branch protection and it does NOT make that check required.
|
|
210231
|
+
# Turn the check on in repo settings to enforce (${ENFORCEMENT_DOC}).
|
|
210232
|
+
#
|
|
210233
|
+
# Copied from the gate repo example (examples/contract-gate.yml) \u2014 action at @v0.
|
|
210234
|
+
name: CodeRifts Contract Gate
|
|
210235
|
+
|
|
210236
|
+
# Run on EVERY pull request. Do NOT add paths: filters \u2014 a required check that
|
|
210237
|
+
# never runs leaves the PR mergeable (see ENFORCEMENT.md, "fail-closed on absence").
|
|
210238
|
+
on:
|
|
210239
|
+
pull_request:
|
|
210240
|
+
|
|
210241
|
+
permissions:
|
|
210242
|
+
checks: write
|
|
210243
|
+
contents: read
|
|
210244
|
+
|
|
210245
|
+
jobs:
|
|
210246
|
+
contract-gate:
|
|
210247
|
+
runs-on: ubuntu-latest
|
|
210248
|
+
steps:
|
|
210249
|
+
- uses: actions/checkout@v4
|
|
210250
|
+
with:
|
|
210251
|
+
# REQUIRED: the gate derives the change set from git diff base...head.
|
|
210252
|
+
fetch-depth: 0
|
|
210253
|
+
|
|
210254
|
+
- uses: ${GATE_ACTION}
|
|
210255
|
+
with:
|
|
210256
|
+
api-key: \${{ secrets.CODERIFTS_API_KEY }}
|
|
210257
|
+
# api-url: https://app.coderifts.com # override only for self-hosted
|
|
210258
|
+
require-verified-monitoring: 'true'
|
|
210259
|
+
monitoring-attestation: \${{ vars.CODERIFTS_MONITORING_ATTESTATION }}
|
|
210260
|
+
monitoring-keyring: \${{ github.workspace }}/.coderifts/monitoring-keys.json
|
|
210261
|
+
`;
|
|
210262
|
+
function workflowBody(strict) {
|
|
210263
|
+
return strict ? STRICT_WORKFLOW_BODY : WORKFLOW_BODY;
|
|
210264
|
+
}
|
|
210162
210265
|
function writeContractGateWorkflow(opts = {}) {
|
|
210163
210266
|
const outDir = opts.outDir;
|
|
210164
210267
|
const dest = path.join(outDir, WORKFLOW_REL);
|
|
@@ -210169,6 +210272,7 @@ jobs:
|
|
|
210169
210272
|
fs.writeFileSync(p, c, "utf8");
|
|
210170
210273
|
});
|
|
210171
210274
|
const dryRun = !!opts.dryRun;
|
|
210275
|
+
const body = workflowBody(!!opts.strict);
|
|
210172
210276
|
if (exists(dest)) {
|
|
210173
210277
|
let onDisk = "";
|
|
210174
210278
|
try {
|
|
@@ -210176,13 +210280,13 @@ jobs:
|
|
|
210176
210280
|
} catch {
|
|
210177
210281
|
onDisk = "";
|
|
210178
210282
|
}
|
|
210179
|
-
const reason = onDisk ===
|
|
210283
|
+
const reason = onDisk === body ? "already present, identical" : "exists; installer does not clobber workflows";
|
|
210180
210284
|
return { action: "skip", relPath: WORKFLOW_REL, reason, exitCode: 0 };
|
|
210181
210285
|
}
|
|
210182
210286
|
if (dryRun) {
|
|
210183
210287
|
return { action: "create", relPath: WORKFLOW_REL, reason: "created", exitCode: 0, dryRun: true };
|
|
210184
210288
|
}
|
|
210185
|
-
writeFile(dest,
|
|
210289
|
+
writeFile(dest, body);
|
|
210186
210290
|
return { action: "create", relPath: WORKFLOW_REL, reason: "created", exitCode: 0 };
|
|
210187
210291
|
}
|
|
210188
210292
|
function workflowPresent(outDir, deps = {}) {
|
|
@@ -210199,6 +210303,7 @@ jobs:
|
|
|
210199
210303
|
module2.exports = {
|
|
210200
210304
|
WORKFLOW_REL,
|
|
210201
210305
|
WORKFLOW_BODY,
|
|
210306
|
+
STRICT_WORKFLOW_BODY,
|
|
210202
210307
|
GATE_ACTION,
|
|
210203
210308
|
ENFORCEMENT_DOC,
|
|
210204
210309
|
writeContractGateWorkflow,
|
|
@@ -210230,7 +210335,7 @@ var require_init_agents = __commonJS({
|
|
|
210230
210335
|
workflowPresent
|
|
210231
210336
|
} = require_contract_gate_workflow();
|
|
210232
210337
|
if (process.env.NO_COLOR) chalk.level = 0;
|
|
210233
|
-
var USAGE = `Usage: coderifts init --agents [--hosts=claude,cursor,copilot,all] [--no-hook] [--no-workflow] [--dry-run] [--check] [--out <dir>]
|
|
210338
|
+
var USAGE = `Usage: coderifts init --agents [--hosts=claude,cursor,copilot,all] [--no-hook] [--no-workflow] [--strict] [--dry-run] [--check] [--out <dir>]
|
|
210234
210339
|
|
|
210235
210340
|
Wire this repository for governed agent work in ONE command:
|
|
210236
210341
|
1. repo-level MCP config (per host: .mcp.json / .cursor/mcp.json / .vscode/mcp.json)
|
|
@@ -210241,6 +210346,7 @@ var require_init_agents = __commonJS({
|
|
|
210241
210346
|
--hosts <list> claude, cursor, copilot, or all (default: all)
|
|
210242
210347
|
--no-hook Skip host hook install
|
|
210243
210348
|
--no-workflow Skip writing the CI workflow
|
|
210349
|
+
--strict STRICT workflow: require-verified-monitoring: true (default template stays false)
|
|
210244
210350
|
--dry-run Print the plan without writing
|
|
210245
210351
|
--check Report which of the four pieces are present (no write)
|
|
210246
210352
|
--out <dir> Target directory (default: cwd)
|
|
@@ -210300,9 +210406,9 @@ ${USAGE}`
|
|
|
210300
210406
|
return { hosts, allRules: false };
|
|
210301
210407
|
}
|
|
210302
210408
|
function agentFlagsRequireAgents(options = {}) {
|
|
210303
|
-
return !!(options.check || options.dryRun || options.out || options.hosts != null && String(options.hosts) !== "all" || options.hook === false || options.workflow === false || options.noHook === true || options.noWorkflow === true);
|
|
210409
|
+
return !!(options.check || options.dryRun || options.out || options.hosts != null && String(options.hosts) !== "all" || options.hook === false || options.workflow === false || options.noHook === true || options.noWorkflow === true || options.strict === true);
|
|
210304
210410
|
}
|
|
210305
|
-
var AGENT_FLAGS_REQUIRE_AGENTS = "Error: --check/--dry-run/--out/--hosts/--no-hook/--no-workflow require --agents";
|
|
210411
|
+
var AGENT_FLAGS_REQUIRE_AGENTS = "Error: --check/--dry-run/--out/--hosts/--no-hook/--no-workflow/--strict require --agents";
|
|
210306
210412
|
var AGENTS_NO_TEMPLATE = "Error: init --agents does not take a template. Did you mean `coderifts init --agents` (not `coderifts init agent`)?";
|
|
210307
210413
|
function rulePathsForHosts(hosts, allRules) {
|
|
210308
210414
|
const out = [SHARED_RULE];
|
|
@@ -210565,12 +210671,12 @@ ${USAGE}`
|
|
|
210565
210671
|
if (!wantWorkflow(options)) {
|
|
210566
210672
|
items.push({ section: "workflow", action: "skip", relPath: WORKFLOW_REL, note: "--no-workflow" });
|
|
210567
210673
|
} else {
|
|
210568
|
-
const r = writeContractGateWorkflow({ outDir, dryRun, ...fsDeps });
|
|
210674
|
+
const r = writeContractGateWorkflow({ outDir, dryRun, strict: !!options.strict, ...fsDeps });
|
|
210569
210675
|
items.push({
|
|
210570
210676
|
section: "workflow",
|
|
210571
210677
|
action: r.action,
|
|
210572
210678
|
relPath: r.relPath,
|
|
210573
|
-
note: r.action === "create" ? `${GATE_ACTION}` : r.reason
|
|
210679
|
+
note: r.action === "create" ? options.strict ? `${GATE_ACTION} (STRICT require-verified-monitoring)` : GATE_ACTION : r.reason
|
|
210574
210680
|
});
|
|
210575
210681
|
}
|
|
210576
210682
|
const created = items.filter((i) => i.action === "create").length;
|
|
@@ -237167,6 +237273,162 @@ var require_status = __commonJS({
|
|
|
237167
237273
|
}
|
|
237168
237274
|
});
|
|
237169
237275
|
|
|
237276
|
+
// src/provider/workflow-scan.js
|
|
237277
|
+
var require_workflow_scan = __commonJS({
|
|
237278
|
+
"src/provider/workflow-scan.js"(exports2, module2) {
|
|
237279
|
+
"use strict";
|
|
237280
|
+
var yaml = require_js_yaml();
|
|
237281
|
+
var CONTRACT_GATE_ACTION = "coderifts/contract-gate";
|
|
237282
|
+
var DEPLOY_GATE_INVOCATION = /^(?:npx(?:\s+--yes)?\s+|npm\s+exec\s+|pnpm\s+(?:dlx|exec)\s+|yarn(?:\s+(?:dlx|exec))?\s+)?coderifts\s+deploy-gate(?:\s|$)/;
|
|
237283
|
+
var NON_SHELL = /* @__PURE__ */ new Set(["python", "python3", "node", "ruby", "perl", "php", "pwsh", "powershell"]);
|
|
237284
|
+
function isPinnedMajor(ref) {
|
|
237285
|
+
const r = String(ref || "");
|
|
237286
|
+
if (/^v\d+(\.\d+){0,2}$/.test(r)) return true;
|
|
237287
|
+
if (/^[0-9a-f]{40}$/i.test(r)) return true;
|
|
237288
|
+
return false;
|
|
237289
|
+
}
|
|
237290
|
+
function parseActionUses(uses) {
|
|
237291
|
+
const raw = String(uses || "").trim();
|
|
237292
|
+
const at = raw.lastIndexOf("@");
|
|
237293
|
+
if (at <= 0) return { name: raw, ref: "", pinnedMajor: false };
|
|
237294
|
+
return {
|
|
237295
|
+
name: raw.slice(0, at),
|
|
237296
|
+
ref: raw.slice(at + 1),
|
|
237297
|
+
pinnedMajor: isPinnedMajor(raw.slice(at + 1))
|
|
237298
|
+
};
|
|
237299
|
+
}
|
|
237300
|
+
function isContractGateUses(uses) {
|
|
237301
|
+
const parsed = parseActionUses(uses);
|
|
237302
|
+
return parsed.name === CONTRACT_GATE_ACTION && parsed.pinnedMajor === true;
|
|
237303
|
+
}
|
|
237304
|
+
function isExplicitlyDisabled(node) {
|
|
237305
|
+
if (!node || typeof node !== "object" || !Object.prototype.hasOwnProperty.call(node, "if")) {
|
|
237306
|
+
return false;
|
|
237307
|
+
}
|
|
237308
|
+
const ifVal = node.if;
|
|
237309
|
+
if (ifVal === false || ifVal === 0 || ifVal === null) return true;
|
|
237310
|
+
if (typeof ifVal === "string") {
|
|
237311
|
+
const s = ifVal.trim().toLowerCase();
|
|
237312
|
+
if (s === "false" || s === "0" || s === "" || s === "null") return true;
|
|
237313
|
+
if (/^\$\{\{\s*(?:false|0|null)\s*\}\}$/.test(s)) return true;
|
|
237314
|
+
}
|
|
237315
|
+
return false;
|
|
237316
|
+
}
|
|
237317
|
+
function jobEnvironmentName(job) {
|
|
237318
|
+
if (!job || typeof job !== "object") return null;
|
|
237319
|
+
const env = job.environment;
|
|
237320
|
+
if (typeof env === "string") {
|
|
237321
|
+
const name = env.trim();
|
|
237322
|
+
return name || null;
|
|
237323
|
+
}
|
|
237324
|
+
if (env && typeof env === "object" && typeof env.name === "string") {
|
|
237325
|
+
const name = env.name.trim();
|
|
237326
|
+
return name || null;
|
|
237327
|
+
}
|
|
237328
|
+
return null;
|
|
237329
|
+
}
|
|
237330
|
+
function isNonPosixShell(shell) {
|
|
237331
|
+
if (shell == null || shell === "") return false;
|
|
237332
|
+
const first = String(shell).trim().split(/\s+/)[0].replace(/^.*\//, "").toLowerCase();
|
|
237333
|
+
return NON_SHELL.has(first);
|
|
237334
|
+
}
|
|
237335
|
+
function isDeployGateInvocation(run, shell) {
|
|
237336
|
+
if (isNonPosixShell(shell)) return false;
|
|
237337
|
+
const text = String(run || "");
|
|
237338
|
+
let heredoc = null;
|
|
237339
|
+
let quote = null;
|
|
237340
|
+
for (const rawLine of text.split("\n")) {
|
|
237341
|
+
let line = String(rawLine || "");
|
|
237342
|
+
if (heredoc) {
|
|
237343
|
+
if (line.trim() === heredoc) heredoc = null;
|
|
237344
|
+
continue;
|
|
237345
|
+
}
|
|
237346
|
+
const hd = /<<[-]?\s*['"]?(\w+)['"]?/.exec(line);
|
|
237347
|
+
if (hd) heredoc = hd[1];
|
|
237348
|
+
if (quote) {
|
|
237349
|
+
for (let i = 0; i < line.length; i++) {
|
|
237350
|
+
const ch = line[i];
|
|
237351
|
+
if (ch === quote && line[i - 1] !== "\\") {
|
|
237352
|
+
quote = null;
|
|
237353
|
+
break;
|
|
237354
|
+
}
|
|
237355
|
+
}
|
|
237356
|
+
if (quote) continue;
|
|
237357
|
+
}
|
|
237358
|
+
const trimmed = line.trim();
|
|
237359
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
237360
|
+
let cmd = trimmed.replace(/^(?:\w+=\S+\s+)+/, "");
|
|
237361
|
+
if (/^(echo|printf|cat|false|true|:)\b/.test(cmd)) {
|
|
237362
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
237363
|
+
const ch = cmd[i];
|
|
237364
|
+
if ((ch === "'" || ch === '"') && cmd[i - 1] !== "\\") {
|
|
237365
|
+
quote = quote ? quote === ch ? null : quote : ch;
|
|
237366
|
+
}
|
|
237367
|
+
}
|
|
237368
|
+
continue;
|
|
237369
|
+
}
|
|
237370
|
+
if (DEPLOY_GATE_INVOCATION.test(cmd)) return true;
|
|
237371
|
+
}
|
|
237372
|
+
return false;
|
|
237373
|
+
}
|
|
237374
|
+
function stepsOf(job) {
|
|
237375
|
+
if (!job || typeof job !== "object") return [];
|
|
237376
|
+
return Array.isArray(job.steps) ? job.steps : [];
|
|
237377
|
+
}
|
|
237378
|
+
function inspectJob(job) {
|
|
237379
|
+
const out = {
|
|
237380
|
+
contractGate: false,
|
|
237381
|
+
deployGate: false,
|
|
237382
|
+
environment: jobEnvironmentName(job),
|
|
237383
|
+
disabled: isExplicitlyDisabled(job)
|
|
237384
|
+
};
|
|
237385
|
+
if (out.disabled) return out;
|
|
237386
|
+
for (const step of stepsOf(job)) {
|
|
237387
|
+
if (!step || typeof step !== "object") continue;
|
|
237388
|
+
if (isExplicitlyDisabled(step)) continue;
|
|
237389
|
+
if (step.uses != null && isContractGateUses(step.uses)) out.contractGate = true;
|
|
237390
|
+
if (step.run != null && isDeployGateInvocation(step.run, step.shell)) out.deployGate = true;
|
|
237391
|
+
}
|
|
237392
|
+
return out;
|
|
237393
|
+
}
|
|
237394
|
+
function inspectWorkflowYaml(body) {
|
|
237395
|
+
const empty = { contractGate: false, deployGate: false, deployEnvironments: [] };
|
|
237396
|
+
let doc;
|
|
237397
|
+
try {
|
|
237398
|
+
doc = yaml.load(String(body || ""));
|
|
237399
|
+
} catch {
|
|
237400
|
+
return empty;
|
|
237401
|
+
}
|
|
237402
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) return empty;
|
|
237403
|
+
const jobs = doc.jobs && typeof doc.jobs === "object" && !Array.isArray(doc.jobs) ? doc.jobs : null;
|
|
237404
|
+
if (!jobs) return empty;
|
|
237405
|
+
let contractGate = false;
|
|
237406
|
+
let deployGate = false;
|
|
237407
|
+
const deployEnvironments = [];
|
|
237408
|
+
for (const name of Object.keys(jobs)) {
|
|
237409
|
+
const info = inspectJob(jobs[name]);
|
|
237410
|
+
if (info.contractGate) contractGate = true;
|
|
237411
|
+
if (info.deployGate) {
|
|
237412
|
+
deployGate = true;
|
|
237413
|
+
if (info.environment) deployEnvironments.push(info.environment);
|
|
237414
|
+
}
|
|
237415
|
+
}
|
|
237416
|
+
return { contractGate, deployGate, deployEnvironments };
|
|
237417
|
+
}
|
|
237418
|
+
module2.exports = {
|
|
237419
|
+
CONTRACT_GATE_ACTION,
|
|
237420
|
+
isPinnedMajor,
|
|
237421
|
+
parseActionUses,
|
|
237422
|
+
isContractGateUses,
|
|
237423
|
+
isExplicitlyDisabled,
|
|
237424
|
+
jobEnvironmentName,
|
|
237425
|
+
isDeployGateInvocation,
|
|
237426
|
+
inspectJob,
|
|
237427
|
+
inspectWorkflowYaml
|
|
237428
|
+
};
|
|
237429
|
+
}
|
|
237430
|
+
});
|
|
237431
|
+
|
|
237170
237432
|
// src/provider/github-enforcement.js
|
|
237171
237433
|
var require_github_enforcement = __commonJS({
|
|
237172
237434
|
"src/provider/github-enforcement.js"(exports2, module2) {
|
|
@@ -237178,7 +237440,107 @@ var require_github_enforcement = __commonJS({
|
|
|
237178
237440
|
extractRequiredContexts
|
|
237179
237441
|
} = require_setup_required_check();
|
|
237180
237442
|
var { GATE_ACTION } = require_contract_gate_workflow();
|
|
237181
|
-
var
|
|
237443
|
+
var { inspectWorkflowYaml } = require_workflow_scan();
|
|
237444
|
+
var EVIDENCE_SPEC = "provider-enforcement-evidence.v2";
|
|
237445
|
+
var CODERIFTS_GITHUB_APP_SLUG = "coderifts";
|
|
237446
|
+
var CODERIFTS_GITHUB_APP_ID = 2860592;
|
|
237447
|
+
var REASON = Object.freeze({
|
|
237448
|
+
ISSUER_BOUND: "issuer_bound",
|
|
237449
|
+
NO_ISSUER_BINDING: "no_issuer_binding",
|
|
237450
|
+
LEGACY_CONTEXTS_NO_ISSUER_BINDING: "legacy_contexts_no_issuer_binding",
|
|
237451
|
+
ISSUER_APP_ID_MISMATCH: "issuer_app_id_mismatch",
|
|
237452
|
+
ISSUER_APP_ID_UNRESOLVABLE: "issuer_app_id_unresolvable",
|
|
237453
|
+
REQUIRED_CHECK_ABSENT: "required_check_absent",
|
|
237454
|
+
ACTIVE_JOB: "active_job",
|
|
237455
|
+
NO_ACTIVE_JOB: "no_active_job",
|
|
237456
|
+
ENVIRONMENT_CORRELATED: "environment_correlated",
|
|
237457
|
+
ENVIRONMENT_MISMATCH: "environment_mismatch"
|
|
237458
|
+
});
|
|
237459
|
+
function verifiedRequiresBinding(layer2) {
|
|
237460
|
+
if (!layer2 || layer2.status !== STATUS.VERIFIED) return true;
|
|
237461
|
+
if (layer2.id === "required_check") return layer2.reason_code === REASON.ISSUER_BOUND;
|
|
237462
|
+
if (layer2.id === "workflow_contract_gate" || layer2.id === "workflow_deploy_gate") {
|
|
237463
|
+
return layer2.reason_code === REASON.ACTIVE_JOB;
|
|
237464
|
+
}
|
|
237465
|
+
if (layer2.id === "environment_protection") {
|
|
237466
|
+
return layer2.reason_code === REASON.ENVIRONMENT_CORRELATED;
|
|
237467
|
+
}
|
|
237468
|
+
return true;
|
|
237469
|
+
}
|
|
237470
|
+
function expectedIssuerAppId() {
|
|
237471
|
+
const id = Number(CODERIFTS_GITHUB_APP_ID);
|
|
237472
|
+
return Number.isInteger(id) && id > 0 ? id : null;
|
|
237473
|
+
}
|
|
237474
|
+
function ourCheckEntries(rsc) {
|
|
237475
|
+
if (!rsc || !Array.isArray(rsc.checks)) return [];
|
|
237476
|
+
return rsc.checks.filter((c) => c && typeof c.context === "string" && c.context === CHECK_NAME);
|
|
237477
|
+
}
|
|
237478
|
+
function numericAppId(value) {
|
|
237479
|
+
if (value == null || value === "") return null;
|
|
237480
|
+
const n = Number(value);
|
|
237481
|
+
return Number.isInteger(n) && n > 0 ? n : null;
|
|
237482
|
+
}
|
|
237483
|
+
function evaluateRequiredCheck(protection) {
|
|
237484
|
+
const expected = expectedIssuerAppId();
|
|
237485
|
+
const rsc = protection && protection.required_status_checks;
|
|
237486
|
+
const contexts = extractRequiredContexts(protection);
|
|
237487
|
+
if (expected == null) {
|
|
237488
|
+
return {
|
|
237489
|
+
status: STATUS.UNVERIFIABLE,
|
|
237490
|
+
reason_code: REASON.ISSUER_APP_ID_UNRESOLVABLE,
|
|
237491
|
+
reason: "CodeRifts GitHub App id could not be determined from public metadata; required_check is UNVERIFIABLE (never VERIFIED without an issuer id)",
|
|
237492
|
+
required_contexts: contexts,
|
|
237493
|
+
app_id: null
|
|
237494
|
+
};
|
|
237495
|
+
}
|
|
237496
|
+
const ours = ourCheckEntries(rsc);
|
|
237497
|
+
if (ours.length > 0) {
|
|
237498
|
+
const bound = ours.find((c) => numericAppId(c.app_id) === expected);
|
|
237499
|
+
if (bound) {
|
|
237500
|
+
return {
|
|
237501
|
+
status: STATUS.VERIFIED,
|
|
237502
|
+
reason_code: REASON.ISSUER_BOUND,
|
|
237503
|
+
reason: `required_status_checks.checks binds "${CHECK_NAME}" to GitHub App id ${expected} (slug ${CODERIFTS_GITHUB_APP_SLUG})`,
|
|
237504
|
+
required_contexts: contexts,
|
|
237505
|
+
app_id: expected
|
|
237506
|
+
};
|
|
237507
|
+
}
|
|
237508
|
+
const other = ours.find((c) => numericAppId(c.app_id) != null);
|
|
237509
|
+
if (other) {
|
|
237510
|
+
return {
|
|
237511
|
+
status: STATUS.NOT_VERIFIED,
|
|
237512
|
+
reason_code: REASON.ISSUER_APP_ID_MISMATCH,
|
|
237513
|
+
reason: `required_status_checks.checks binds "${CHECK_NAME}" to app_id ${numericAppId(other.app_id)}, not CodeRifts App id ${expected}`,
|
|
237514
|
+
required_contexts: contexts,
|
|
237515
|
+
app_id: numericAppId(other.app_id),
|
|
237516
|
+
expected_app_id: expected
|
|
237517
|
+
};
|
|
237518
|
+
}
|
|
237519
|
+
return {
|
|
237520
|
+
status: STATUS.NOT_VERIFIED,
|
|
237521
|
+
reason_code: REASON.NO_ISSUER_BINDING,
|
|
237522
|
+
reason: `required_status_checks.checks lists "${CHECK_NAME}" with no app_id (name-only; any repository writer can post a success status with that context)`,
|
|
237523
|
+
required_contexts: contexts,
|
|
237524
|
+
app_id: null
|
|
237525
|
+
};
|
|
237526
|
+
}
|
|
237527
|
+
if (contexts.includes(CHECK_NAME)) {
|
|
237528
|
+
return {
|
|
237529
|
+
status: STATUS.NOT_VERIFIED,
|
|
237530
|
+
reason_code: REASON.LEGACY_CONTEXTS_NO_ISSUER_BINDING,
|
|
237531
|
+
reason: `required_status_checks.contexts lists "${CHECK_NAME}" but the legacy contexts array has no app_id at all (legacy_contexts_no_issuer_binding)`,
|
|
237532
|
+
required_contexts: contexts,
|
|
237533
|
+
app_id: null
|
|
237534
|
+
};
|
|
237535
|
+
}
|
|
237536
|
+
return {
|
|
237537
|
+
status: STATUS.NOT_VERIFIED,
|
|
237538
|
+
reason_code: REASON.REQUIRED_CHECK_ABSENT,
|
|
237539
|
+
reason: `required_status_checks does not include "${CHECK_NAME}" (have: ${contexts.join(", ") || "none"})`,
|
|
237540
|
+
required_contexts: contexts,
|
|
237541
|
+
app_id: null
|
|
237542
|
+
};
|
|
237543
|
+
}
|
|
237182
237544
|
var APP_INSTALLATION = Object.freeze({
|
|
237183
237545
|
can_query_protection: false,
|
|
237184
237546
|
permissions: Object.freeze([
|
|
@@ -237344,12 +237706,14 @@ var require_github_enforcement = __commonJS({
|
|
|
237344
237706
|
} else {
|
|
237345
237707
|
const protection = prot.body && typeof prot.body === "object" ? prot.body : {};
|
|
237346
237708
|
const endpoint = prot.endpoint ? `GET ${prot.endpoint}` : null;
|
|
237347
|
-
const
|
|
237348
|
-
|
|
237349
|
-
|
|
237350
|
-
|
|
237709
|
+
const required = evaluateRequiredCheck(protection);
|
|
237710
|
+
layers.push(layer("required_check", required.status, {
|
|
237711
|
+
reason: required.reason,
|
|
237712
|
+
reason_code: required.reason_code,
|
|
237351
237713
|
endpoint,
|
|
237352
|
-
required_contexts:
|
|
237714
|
+
required_contexts: required.required_contexts,
|
|
237715
|
+
app_id: required.app_id,
|
|
237716
|
+
...required.expected_app_id != null ? { expected_app_id: required.expected_app_id } : {}
|
|
237353
237717
|
}));
|
|
237354
237718
|
const admins = enforceAdminsEnabled(protection);
|
|
237355
237719
|
layers.push(layer("enforce_admins", admins ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
@@ -237401,24 +237765,55 @@ var require_github_enforcement = __commonJS({
|
|
|
237401
237765
|
}));
|
|
237402
237766
|
} else {
|
|
237403
237767
|
const ok = environmentProtected(environmentRead.body);
|
|
237404
|
-
|
|
237405
|
-
|
|
237406
|
-
|
|
237407
|
-
|
|
237408
|
-
|
|
237768
|
+
const referenced = (localFiles.deployGateEnvironments || []).map(String);
|
|
237769
|
+
const correlated = ok && environmentName && referenced.includes(environmentName);
|
|
237770
|
+
if (correlated) {
|
|
237771
|
+
layers.push(layer("environment_protection", STATUS.VERIFIED, {
|
|
237772
|
+
reason: `GitHub Environment "${environmentName}" has protection rules and is referenced by an active deploy-gate job (environment:)`,
|
|
237773
|
+
reason_code: REASON.ENVIRONMENT_CORRELATED,
|
|
237774
|
+
endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
|
|
237775
|
+
environment: environmentName,
|
|
237776
|
+
workflow_environments: referenced
|
|
237777
|
+
}));
|
|
237778
|
+
} else if (!ok) {
|
|
237779
|
+
layers.push(layer("environment_protection", STATUS.NOT_VERIFIED, {
|
|
237780
|
+
reason: `GitHub Environment "${environmentName}" exists but has no protection_rules and no deployment_branch_policy`,
|
|
237781
|
+
reason_code: REASON.ENVIRONMENT_MISMATCH,
|
|
237782
|
+
endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
|
|
237783
|
+
environment: environmentName,
|
|
237784
|
+
workflow_environments: referenced
|
|
237785
|
+
}));
|
|
237786
|
+
} else {
|
|
237787
|
+
layers.push(layer("environment_protection", STATUS.NOT_VERIFIED, {
|
|
237788
|
+
reason: `GitHub Environment "${environmentName}" is protected but no active deploy-gate job sets environment: ${environmentName} (have: ${referenced.join(", ") || "none"})`,
|
|
237789
|
+
reason_code: REASON.ENVIRONMENT_MISMATCH,
|
|
237790
|
+
endpoint: environmentRead.endpoint ? `GET ${environmentRead.endpoint}` : null,
|
|
237791
|
+
environment: environmentName,
|
|
237792
|
+
workflow_environments: referenced
|
|
237793
|
+
}));
|
|
237794
|
+
}
|
|
237409
237795
|
}
|
|
237410
237796
|
const cg = localFiles.contractGateFiles || [];
|
|
237411
237797
|
layers.push(layer("workflow_contract_gate", cg.length ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
237412
|
-
reason: cg.length ? `
|
|
237798
|
+
reason: cg.length ? `active job step uses ${GATE_ACTION} (${cg.join(", ")})` : `no active .github/workflows job uses ${GATE_ACTION} (comments, disabled jobs, and string literals do not count)`,
|
|
237799
|
+
reason_code: cg.length ? REASON.ACTIVE_JOB : REASON.NO_ACTIVE_JOB,
|
|
237413
237800
|
endpoint: "local:.github/workflows",
|
|
237414
237801
|
files: cg
|
|
237415
237802
|
}));
|
|
237416
237803
|
const dg = localFiles.deployGateFiles || [];
|
|
237417
237804
|
layers.push(layer("workflow_deploy_gate", dg.length ? STATUS.VERIFIED : STATUS.NOT_VERIFIED, {
|
|
237418
|
-
reason: dg.length ? `
|
|
237805
|
+
reason: dg.length ? `active job step invokes coderifts deploy-gate (${dg.join(", ")})` : "no active .github/workflows job invokes coderifts deploy-gate (comments, disabled jobs, and string literals do not count)",
|
|
237806
|
+
reason_code: dg.length ? REASON.ACTIVE_JOB : REASON.NO_ACTIVE_JOB,
|
|
237419
237807
|
endpoint: "local:.github/workflows",
|
|
237420
237808
|
files: dg
|
|
237421
237809
|
}));
|
|
237810
|
+
for (const l of layers) {
|
|
237811
|
+
if (l.status === STATUS.VERIFIED && !verifiedRequiresBinding(l)) {
|
|
237812
|
+
l.status = STATUS.NOT_VERIFIED;
|
|
237813
|
+
l.reason_code = l.reason_code || REASON.NO_ISSUER_BINDING;
|
|
237814
|
+
l.reason = `${l.reason || l.id} \u2014 downgraded: VERIFIED requires issuer or active-job binding`;
|
|
237815
|
+
}
|
|
237816
|
+
}
|
|
237422
237817
|
const byId = Object.fromEntries(layers.map((l) => [l.id, l]));
|
|
237423
237818
|
const inescapable = LAYER_IDS.every((id) => byId[id] && byId[id].status === STATUS.VERIFIED);
|
|
237424
237819
|
return {
|
|
@@ -237426,7 +237821,7 @@ var require_github_enforcement = __commonJS({
|
|
|
237426
237821
|
inescapable_deploy: inescapable,
|
|
237427
237822
|
claim: {
|
|
237428
237823
|
inescapable_deploy: inescapable,
|
|
237429
|
-
basis: inescapable ? "all six layers VERIFIED
|
|
237824
|
+
basis: inescapable ? "all six layers VERIFIED (issuer-bound required check + active workflow jobs + correlated environment)" : "inescapable_deploy stays false until every layer is VERIFIED"
|
|
237430
237825
|
}
|
|
237431
237826
|
};
|
|
237432
237827
|
}
|
|
@@ -237437,12 +237832,13 @@ var require_github_enforcement = __commonJS({
|
|
|
237437
237832
|
const dir = path.join(cwd, ".github", "workflows");
|
|
237438
237833
|
const contractGateFiles = [];
|
|
237439
237834
|
const deployGateFiles = [];
|
|
237440
|
-
|
|
237835
|
+
const deployGateEnvironments = [];
|
|
237836
|
+
if (!exists(dir)) return { contractGateFiles, deployGateFiles, deployGateEnvironments };
|
|
237441
237837
|
let names = [];
|
|
237442
237838
|
try {
|
|
237443
237839
|
names = readdir(dir);
|
|
237444
237840
|
} catch {
|
|
237445
|
-
return { contractGateFiles, deployGateFiles };
|
|
237841
|
+
return { contractGateFiles, deployGateFiles, deployGateEnvironments };
|
|
237446
237842
|
}
|
|
237447
237843
|
for (const name of names) {
|
|
237448
237844
|
if (!/\.ya?ml$/i.test(name)) continue;
|
|
@@ -237453,10 +237849,18 @@ var require_github_enforcement = __commonJS({
|
|
|
237453
237849
|
} catch {
|
|
237454
237850
|
continue;
|
|
237455
237851
|
}
|
|
237456
|
-
|
|
237457
|
-
if (
|
|
237852
|
+
const evidence = inspectWorkflowYaml(body);
|
|
237853
|
+
if (evidence.contractGate) contractGateFiles.push(rel);
|
|
237854
|
+
if (evidence.deployGate) {
|
|
237855
|
+
deployGateFiles.push(rel);
|
|
237856
|
+
for (const envName of evidence.deployEnvironments) {
|
|
237857
|
+
if (envName && !deployGateEnvironments.includes(envName)) {
|
|
237858
|
+
deployGateEnvironments.push(envName);
|
|
237859
|
+
}
|
|
237860
|
+
}
|
|
237861
|
+
}
|
|
237458
237862
|
}
|
|
237459
|
-
return { contractGateFiles, deployGateFiles };
|
|
237863
|
+
return { contractGateFiles, deployGateFiles, deployGateEnvironments };
|
|
237460
237864
|
}
|
|
237461
237865
|
async function queryGitHubEnforcement({
|
|
237462
237866
|
owner,
|
|
@@ -237491,12 +237895,17 @@ var require_github_enforcement = __commonJS({
|
|
|
237491
237895
|
NO_TOKEN_INSTRUCTION,
|
|
237492
237896
|
LAYER_IDS,
|
|
237493
237897
|
STATUS,
|
|
237898
|
+
REASON,
|
|
237494
237899
|
CHECK_NAME,
|
|
237495
237900
|
GATE_ACTION,
|
|
237901
|
+
CODERIFTS_GITHUB_APP_ID,
|
|
237902
|
+
CODERIFTS_GITHUB_APP_SLUG,
|
|
237496
237903
|
resolveGitHubAuth,
|
|
237497
237904
|
redactSecrets,
|
|
237498
237905
|
createGitHubHttpClient,
|
|
237499
237906
|
evaluateLayers,
|
|
237907
|
+
evaluateRequiredCheck,
|
|
237908
|
+
verifiedRequiresBinding,
|
|
237500
237909
|
scanLocalWorkflows,
|
|
237501
237910
|
queryGitHubEnforcement,
|
|
237502
237911
|
enforceAdminsEnabled,
|
|
@@ -237572,7 +237981,8 @@ var require_enforce_check = __commonJS({
|
|
|
237572
237981
|
const missing = report.layers.filter((l) => l.status !== "VERIFIED");
|
|
237573
237982
|
for (const l of missing) {
|
|
237574
237983
|
if (l.id === "required_check") {
|
|
237575
|
-
lines.push(chalk.dim(' require "CodeRifts / contract-gate"
|
|
237984
|
+
lines.push(chalk.dim(' require "CodeRifts / contract-gate" bound to the CodeRifts GitHub App (app_id)'));
|
|
237985
|
+
lines.push(chalk.dim(" on default-branch protection \u2014 name-only and legacy contexts[] are NOT_VERIFIED"));
|
|
237576
237986
|
lines.push(chalk.dim(" (coderifts setup-required-check --apply)"));
|
|
237577
237987
|
} else if (l.id === "enforce_admins") {
|
|
237578
237988
|
lines.push(chalk.dim(" set enforce_admins: true so administrators cannot bypass"));
|
|
@@ -237580,12 +237990,13 @@ var require_enforce_check = __commonJS({
|
|
|
237580
237990
|
} else if (l.id === "strict_status_checks") {
|
|
237581
237991
|
lines.push(chalk.dim(" set required_status_checks.strict: true"));
|
|
237582
237992
|
} else if (l.id === "environment_protection") {
|
|
237583
|
-
lines.push(chalk.dim(" add a GitHub Environment with protection rules, pass --env <name
|
|
237993
|
+
lines.push(chalk.dim(" add a GitHub Environment with protection rules, pass --env <name>,"));
|
|
237994
|
+
lines.push(chalk.dim(" and set that same name on the deploy-gate job's environment: key"));
|
|
237584
237995
|
} else if (l.id === "workflow_contract_gate") {
|
|
237585
|
-
lines.push(chalk.dim(" add
|
|
237586
|
-
lines.push(chalk.dim(" (coderifts init --agents)"));
|
|
237996
|
+
lines.push(chalk.dim(" add an active job step: uses: coderifts/contract-gate@v0"));
|
|
237997
|
+
lines.push(chalk.dim(" (comments / if: false / wrong org do not count; coderifts init --agents)"));
|
|
237587
237998
|
} else if (l.id === "workflow_deploy_gate") {
|
|
237588
|
-
lines.push(chalk.dim(" add
|
|
237999
|
+
lines.push(chalk.dim(" add an active CD step that invokes: coderifts deploy-gate --env <env> --artifact <id> --receipt <file>"));
|
|
237589
238000
|
}
|
|
237590
238001
|
}
|
|
237591
238002
|
}
|
|
@@ -240719,7 +241130,7 @@ program.command("registry-gate [dir]").description("Admit a directory of OpenAPI
|
|
|
240719
241130
|
process.exitCode = code;
|
|
240720
241131
|
process.exit(code);
|
|
240721
241132
|
});
|
|
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) => {
|
|
241133
|
+
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("--strict", "With --agents: STRICT workflow (require-verified-monitoring: true)").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) => {
|
|
240723
241134
|
const {
|
|
240724
241135
|
runInitAgents,
|
|
240725
241136
|
agentFlagsRequireAgents,
|
|
@@ -240738,7 +241149,8 @@ program.command("init [template]").description("Generate a .coderifts.yml from a
|
|
|
240738
241149
|
workflow: options.workflow,
|
|
240739
241150
|
dryRun: !!options.dryRun,
|
|
240740
241151
|
check: !!options.check,
|
|
240741
|
-
out: options.out
|
|
241152
|
+
out: options.out,
|
|
241153
|
+
strict: !!options.strict
|
|
240742
241154
|
});
|
|
240743
241155
|
if (result && typeof result.exitCode === "number") {
|
|
240744
241156
|
process.exitCode = result.exitCode;
|
package/package.json
CHANGED