coderifts 4.2.1 → 4.3.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 +32 -0
- package/README.md +29 -14
- package/bin/coderifts.js +6 -6
- package/dist/cli.js +1868 -1793
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3007,7 +3007,7 @@ var require_package = __commonJS({
|
|
|
3007
3007
|
"package.json"(exports2, module2) {
|
|
3008
3008
|
module2.exports = {
|
|
3009
3009
|
name: "coderifts",
|
|
3010
|
-
version: "4.
|
|
3010
|
+
version: "4.3.0",
|
|
3011
3011
|
description: "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
|
|
3012
3012
|
author: "CodeRifts <hello@coderifts.com>",
|
|
3013
3013
|
license: "MIT",
|
|
@@ -71780,1137 +71780,1850 @@ var require_cjs3 = __commonJS({
|
|
|
71780
71780
|
}
|
|
71781
71781
|
});
|
|
71782
71782
|
|
|
71783
|
-
// src/commands/
|
|
71784
|
-
var
|
|
71785
|
-
"src/commands/
|
|
71783
|
+
// src/commands/claude-hook.js
|
|
71784
|
+
var require_claude_hook = __commonJS({
|
|
71785
|
+
"src/commands/claude-hook.js"(exports2, module2) {
|
|
71786
71786
|
"use strict";
|
|
71787
71787
|
var fs = require("fs");
|
|
71788
71788
|
var path = require("path");
|
|
71789
|
-
var
|
|
71790
|
-
var {
|
|
71791
|
-
var {
|
|
71792
|
-
|
|
71793
|
-
var
|
|
71794
|
-
|
|
71795
|
-
|
|
71789
|
+
var { execSync } = require("child_process");
|
|
71790
|
+
var { getApiKey } = require_config();
|
|
71791
|
+
var { cloudAuthorizePreflight } = require_cloud();
|
|
71792
|
+
var DEFAULT_SPEC_PATH = "api/openapi.yaml";
|
|
71793
|
+
var CLOSED_ACTIONS = /* @__PURE__ */ new Set([
|
|
71794
|
+
"CONTINUE",
|
|
71795
|
+
"CONTINUE_WITH_MONITORING",
|
|
71796
|
+
"REQUEST_APPROVAL",
|
|
71797
|
+
"STOP"
|
|
71798
|
+
]);
|
|
71799
|
+
var USAGE = [
|
|
71800
|
+
"Usage: coderifts claude-hook",
|
|
71801
|
+
"",
|
|
71802
|
+
"Claude Code PreToolUse hook: gates Write|Edit|MultiEdit when they touch the",
|
|
71803
|
+
"configured contract spec path. Reads JSON context from STDIN.",
|
|
71804
|
+
"",
|
|
71805
|
+
"Exit map (Claude Code semantics \u2014 get this exactly right):",
|
|
71806
|
+
" 2 BLOCK \u2014 tool call cancelled (BLOCK/STOP, REQUEST_APPROVAL, unrecognised action,",
|
|
71807
|
+
" governance could not run, CONTINUE_WITH_MONITORING without a wired sink)",
|
|
71808
|
+
" 0 allow \u2014 CONTINUE, or CWM with a host-asserted monitoring sink, or non-contract skip",
|
|
71809
|
+
" 1 NEVER used for security deny (Claude treats exit 1 as non-blocking; action proceeds)",
|
|
71810
|
+
"",
|
|
71811
|
+
"Fail-closed DEFAULT (exit 2, reason enforce_indeterminate) when governance could not run:",
|
|
71812
|
+
" no API key / API unreachable / disk unreadable / edit-apply failure on the spec path",
|
|
71813
|
+
" Absence of a key is not permission. Explicit opt-out: CODERIFTS_ADVISORY=1|true.",
|
|
71814
|
+
"",
|
|
71815
|
+
"Parse-gap DEFAULT (exit 2): unparseable stdin / missing file_path \u2014 refusing (fail-closed);",
|
|
71816
|
+
" set CODERIFTS_ADVISORY=1 to soften. JSONL hook_blocked cause stdin_unparseable|missing_file_path.",
|
|
71817
|
+
"",
|
|
71818
|
+
"Still exit 0 (nothing to govern): non-spec path, identical content.",
|
|
71819
|
+
"",
|
|
71820
|
+
"CONTINUE_WITH_MONITORING: allow only if the host asserts a sink",
|
|
71821
|
+
" (CODERIFTS_MONITORING_SINK_WIRED=1|true or git config coderifts.monitoringSinkWired).",
|
|
71822
|
+
" Host claim \u2014 not delivery proof (same class as GuardConfig.monitoringSinkWired).",
|
|
71823
|
+
"",
|
|
71824
|
+
"CODERIFTS_STRICT=1|true: same fail-closed as default; cannot be weakened by ADVISORY.",
|
|
71825
|
+
"",
|
|
71826
|
+
"Spec path (same source as git pre-push hook):",
|
|
71827
|
+
" git config coderifts.specPath (default: api/openapi.yaml)",
|
|
71828
|
+
"",
|
|
71829
|
+
"Baseline at PreToolUse: before = current file on disk (pre-edit state \u2014 the tool has",
|
|
71830
|
+
"not written yet). after = proposed content from tool_input (Write: content; Edit/MultiEdit:",
|
|
71831
|
+
"old\u2192new applied to disk content).",
|
|
71832
|
+
"",
|
|
71833
|
+
"Push-time equivalent: coderifts hook install (git exit 1 on BLOCK)."
|
|
71834
|
+
].join("\n");
|
|
71835
|
+
function isEnvFlag(env, name) {
|
|
71836
|
+
const v = env && env[name];
|
|
71837
|
+
if (v == null || v === "") return false;
|
|
71838
|
+
const s = String(v).trim().toLowerCase();
|
|
71839
|
+
return s === "1" || s === "true";
|
|
71796
71840
|
}
|
|
71797
|
-
function
|
|
71798
|
-
|
|
71799
|
-
let enforcement;
|
|
71800
|
-
if (enforceSignal(options)) enforcement = "ENFORCING";
|
|
71801
|
-
else if (envVal === "unknown") enforcement = "UNKNOWN";
|
|
71802
|
-
else enforcement = "ADVISORY";
|
|
71803
|
-
const bypass_possible = String(process.env.CODERIFTS_DEPLOY_NO_BYPASS || "").toLowerCase() !== "true";
|
|
71804
|
-
return {
|
|
71805
|
-
enforcement,
|
|
71806
|
-
bypass_possible,
|
|
71807
|
-
step_is_required: enforcement === "ENFORCING",
|
|
71808
|
-
required_step_name: "CodeRifts / deploy-gate",
|
|
71809
|
-
attestation_source: "cli_flag"
|
|
71810
|
-
};
|
|
71841
|
+
function isStrictMode(env = process.env) {
|
|
71842
|
+
return isEnvFlag(env, "CODERIFTS_STRICT");
|
|
71811
71843
|
}
|
|
71812
|
-
function
|
|
71813
|
-
|
|
71814
|
-
|
|
71815
|
-
|
|
71816
|
-
|
|
71817
|
-
|
|
71818
|
-
|
|
71819
|
-
|
|
71820
|
-
|
|
71821
|
-
|
|
71844
|
+
function isAdvisoryMode(env = process.env) {
|
|
71845
|
+
return isEnvFlag(env, "CODERIFTS_ADVISORY");
|
|
71846
|
+
}
|
|
71847
|
+
function isMonitoringSinkWired({ env = process.env, deps = {}, result = null } = {}) {
|
|
71848
|
+
if (deps && deps.monitoringSinkWired === true) return true;
|
|
71849
|
+
if (isEnvFlag(env, "CODERIFTS_MONITORING_SINK_WIRED")) return true;
|
|
71850
|
+
const git = readGitConfig("coderifts.monitoringSinkWired", deps);
|
|
71851
|
+
if (git) {
|
|
71852
|
+
const s = String(git).trim().toLowerCase();
|
|
71853
|
+
if (s === "1" || s === "true") return true;
|
|
71822
71854
|
}
|
|
71823
|
-
return
|
|
71855
|
+
if (result && result.monitoringSinkWired === true) return true;
|
|
71856
|
+
const dr = result && result.decision_result;
|
|
71857
|
+
if (dr && typeof dr === "object" && dr.monitoringSinkWired === true) return true;
|
|
71858
|
+
return false;
|
|
71824
71859
|
}
|
|
71825
|
-
|
|
71826
|
-
|
|
71860
|
+
var HOOK_TRIGGER_SOURCE = "claude_hook";
|
|
71861
|
+
function extractFingerprint(result) {
|
|
71862
|
+
const d = result && typeof result === "object" && !Array.isArray(result) ? result : {};
|
|
71863
|
+
const dr = d.decision_result && typeof d.decision_result === "object" ? d.decision_result : {};
|
|
71864
|
+
const fp = dr.fingerprint || d.fingerprint || d.verdict_fingerprint || d.input_fingerprint;
|
|
71865
|
+
return typeof fp === "string" && fp.length > 0 ? fp : void 0;
|
|
71827
71866
|
}
|
|
71828
|
-
function
|
|
71829
|
-
|
|
71830
|
-
|
|
71831
|
-
|
|
71832
|
-
|
|
71833
|
-
|
|
71834
|
-
|
|
71835
|
-
attested_enforcement,
|
|
71836
|
-
gate: null,
|
|
71837
|
-
report_residuals: [],
|
|
71838
|
-
coverage_deploy_input: deployCoverageInput(attested_enforcement, false)
|
|
71839
|
-
};
|
|
71840
|
-
}
|
|
71841
|
-
const requiredContext = {
|
|
71842
|
-
operation: "deploy",
|
|
71843
|
-
enforcement: {
|
|
71844
|
-
enforcement: attested_enforcement,
|
|
71845
|
-
// fail-closed: bypass is possible unless observation proved it disabled.
|
|
71846
|
-
bypass_possible: !(observed_cd_enforcement && observed_cd_enforcement.bypass_possible === false)
|
|
71847
|
-
}
|
|
71848
|
-
};
|
|
71849
|
-
let change_set_rebound = false;
|
|
71850
|
-
if (attested_enforcement === "ENFORCING") {
|
|
71851
|
-
if (expected_fingerprint != null) {
|
|
71852
|
-
requiredContext.expected_fingerprint = expected_fingerprint;
|
|
71853
|
-
change_set_rebound = true;
|
|
71854
|
-
}
|
|
71855
|
-
if (expected_body_hash != null) requiredContext.expected_body_hash = expected_body_hash;
|
|
71867
|
+
function fingerprintPrefix(fp) {
|
|
71868
|
+
if (typeof fp !== "string" || fp.length === 0) return void 0;
|
|
71869
|
+
const colon = fp.indexOf(":");
|
|
71870
|
+
if (colon > 0 && colon < fp.length - 1) {
|
|
71871
|
+
const scheme = fp.slice(0, colon + 1);
|
|
71872
|
+
const rest = fp.slice(colon + 1);
|
|
71873
|
+
return scheme + rest.slice(0, 12) + (rest.length > 12 ? "\u2026" : "");
|
|
71856
71874
|
}
|
|
71857
|
-
|
|
71858
|
-
const enforcement_inescapable = attested_enforcement === "ENFORCING" && !!(observed_cd_enforcement && observed_cd_enforcement.bypass_possible === false);
|
|
71859
|
-
const inescapable_deploy = gate.inescapable_deploy === true && change_set_rebound === true;
|
|
71860
|
-
return {
|
|
71861
|
-
deploy_check_status: gate.state,
|
|
71862
|
-
reason: gate.reason,
|
|
71863
|
-
must_re_preflight: REPAIRABLE.has(gate.reason),
|
|
71864
|
-
attested_enforcement,
|
|
71865
|
-
gate: { deploy_allowed: gate.deploy_allowed, reason: gate.reason, inescapable_deploy },
|
|
71866
|
-
report_residuals: deployReportResiduals(gate.state, enforcement_inescapable, attested_enforcement, change_set_rebound),
|
|
71867
|
-
coverage_deploy_input: deployCoverageInput(attested_enforcement, inescapable_deploy)
|
|
71868
|
-
};
|
|
71875
|
+
return fp.length > 16 ? `${fp.slice(0, 16)}\u2026` : fp;
|
|
71869
71876
|
}
|
|
71870
|
-
function
|
|
71871
|
-
|
|
71872
|
-
|
|
71873
|
-
|
|
71877
|
+
function renderAllowProofBlock({ decision, executionAction, decisionId, fingerprint } = {}) {
|
|
71878
|
+
const decPart = decision ? ` decision=${decision}` : "";
|
|
71879
|
+
const eaPart = executionAction ? ` execution_action=${executionAction}` : "";
|
|
71880
|
+
const idPart = decisionId ? ` decision_id=${decisionId}` : "";
|
|
71881
|
+
const lines = [`CodeRifts claude-hook: AUTHORIZED${decPart}${eaPart}${idPart}`];
|
|
71882
|
+
const pref = fingerprintPrefix(fingerprint);
|
|
71883
|
+
if (pref) lines.push(`- receipt: ${pref}`);
|
|
71884
|
+
lines.push("- Interpretation: authorized at verify time for the bound scope only \u2014 see Limits.");
|
|
71885
|
+
lines.push("- authorized; execution/commit not proven by this hook");
|
|
71886
|
+
return lines.join("\n");
|
|
71874
71887
|
}
|
|
71875
|
-
function
|
|
71876
|
-
|
|
71877
|
-
|
|
71878
|
-
if (!
|
|
71888
|
+
function hookSessionId(env) {
|
|
71889
|
+
const fromEnv = env && env.CODERIFTS_SESSION_ID && String(env.CODERIFTS_SESSION_ID).trim();
|
|
71890
|
+
if (fromEnv) return fromEnv;
|
|
71891
|
+
if (!hookSessionId._gen) hookSessionId._gen = `hook-${process.pid}-${Date.now()}`;
|
|
71892
|
+
return hookSessionId._gen;
|
|
71893
|
+
}
|
|
71894
|
+
function appendGuardEventLog(env, event, deps = {}) {
|
|
71895
|
+
const p = env && env.CODERIFTS_GUARD_EVENT_LOG;
|
|
71896
|
+
if (p == null || String(p).trim() === "") return;
|
|
71897
|
+
const errLog = deps.errLog || ((m) => console.error(String(m)));
|
|
71898
|
+
const append = deps.appendFileSync || ((file, data) => fs.appendFileSync(file, data));
|
|
71879
71899
|
try {
|
|
71880
|
-
|
|
71881
|
-
} catch (
|
|
71882
|
-
|
|
71900
|
+
append(String(p).trim(), JSON.stringify(event) + "\n");
|
|
71901
|
+
} catch (err) {
|
|
71902
|
+
try {
|
|
71903
|
+
errLog(`CodeRifts claude-hook: CODERIFTS_GUARD_EVENT_LOG write failed (${err && err.message})`);
|
|
71904
|
+
} catch {
|
|
71905
|
+
}
|
|
71883
71906
|
}
|
|
71884
71907
|
}
|
|
71885
|
-
function
|
|
71886
|
-
if (
|
|
71887
|
-
|
|
71888
|
-
return
|
|
71908
|
+
function failClosedOrAdvisory({ advisory, strict, site, softMsg, why, errLog }) {
|
|
71909
|
+
if (advisory && !strict) {
|
|
71910
|
+
errLog(`${softMsg} (CODERIFTS_ADVISORY)`);
|
|
71911
|
+
return { exitCode: 0, reason: site, site };
|
|
71889
71912
|
}
|
|
71890
|
-
|
|
71891
|
-
|
|
71892
|
-
|
|
71913
|
+
if (strict) {
|
|
71914
|
+
errLog(
|
|
71915
|
+
`CodeRifts claude-hook: CODERIFTS_STRICT: governance could not run (${why}) \u2014 blocking. Set a key / restore network, or unset CODERIFTS_STRICT.`
|
|
71916
|
+
);
|
|
71917
|
+
return { exitCode: 2, reason: "enforce_indeterminate", site, strictBlocked: true };
|
|
71893
71918
|
}
|
|
71894
|
-
|
|
71895
|
-
|
|
71896
|
-
|
|
71897
|
-
|
|
71898
|
-
return [
|
|
71899
|
-
"# report the deploy outcome after your deploy step:",
|
|
71900
|
-
`coderifts outcome <deploy_succeeded|deploy_failed|rolled_back> --decision ${id}`
|
|
71901
|
-
].join("\n");
|
|
71919
|
+
errLog(
|
|
71920
|
+
`CodeRifts claude-hook: governance could not run (${why}) \u2014 blocking. Set CODERIFTS_ADVISORY=1 to allow without governance (explicit opt-out).`
|
|
71921
|
+
);
|
|
71922
|
+
return { exitCode: 2, reason: "enforce_indeterminate", site };
|
|
71902
71923
|
}
|
|
71903
|
-
function
|
|
71904
|
-
|
|
71905
|
-
|
|
71906
|
-
|
|
71907
|
-
|
|
71908
|
-
|
|
71909
|
-
|
|
71910
|
-
|
|
71911
|
-
|
|
71912
|
-
if (bind.report_residuals.length) lines.push(` Residuals: ${bind.report_residuals.join(", ")}`);
|
|
71913
|
-
if (bind.must_re_preflight) lines.push(chalk.yellow(" Action: re-preflight this { environment, artifact } \u2014 the receipt does not authorize it."));
|
|
71914
|
-
lines.push("");
|
|
71915
|
-
lines.push(enforce ? chalk.dim(" Enforcing \u2014 a failing gate exits non-zero (blocks the deploy step).") : chalk.dim(" Advisory (phase 1) \u2014 does not block the deploy (exit 0)."));
|
|
71916
|
-
lines.push("");
|
|
71917
|
-
return lines.join("\n");
|
|
71924
|
+
function softOrStrictBlock({ strict, reason, softMsg, why, errLog }) {
|
|
71925
|
+
return failClosedOrAdvisory({
|
|
71926
|
+
advisory: false,
|
|
71927
|
+
strict: !!strict,
|
|
71928
|
+
site: reason,
|
|
71929
|
+
softMsg,
|
|
71930
|
+
why,
|
|
71931
|
+
errLog
|
|
71932
|
+
});
|
|
71918
71933
|
}
|
|
71919
|
-
|
|
71920
|
-
const
|
|
71921
|
-
const
|
|
71922
|
-
|
|
71923
|
-
|
|
71924
|
-
|
|
71925
|
-
return;
|
|
71926
|
-
}
|
|
71927
|
-
|
|
71928
|
-
const receipt = readReceiptFile(options.receipt);
|
|
71929
|
-
const observed = observeCDEnforcement({ enforce });
|
|
71930
|
-
const bind = deployBind({ environment, artifact_id: artifactId, receipt, observed_cd_enforcement: observed });
|
|
71931
|
-
const code = clampExit(bind.deploy_check_status, enforce);
|
|
71932
|
-
const decisionId = extractDecisionIdFromReceipt(receipt);
|
|
71933
|
-
if (options.json) {
|
|
71934
|
-
console.log(renderJson({
|
|
71935
|
-
command: "deploy-gate",
|
|
71936
|
-
environment,
|
|
71937
|
-
artifact_id: artifactId,
|
|
71938
|
-
phase: enforce ? "enforcing" : "advisory",
|
|
71939
|
-
exit_code: code,
|
|
71940
|
-
decision_id: decisionId,
|
|
71941
|
-
...bind
|
|
71942
|
-
}));
|
|
71943
|
-
} else {
|
|
71944
|
-
console.log(renderDeployGateTerminal(bind, enforce));
|
|
71945
|
-
if (decisionId) {
|
|
71946
|
-
console.log(formatOutcomeReportHint(decisionId));
|
|
71947
|
-
console.log("");
|
|
71948
|
-
}
|
|
71934
|
+
function readGitConfig(key, deps = {}) {
|
|
71935
|
+
const run = deps.execSync || execSync;
|
|
71936
|
+
const cwd = deps.cwd || process.cwd();
|
|
71937
|
+
try {
|
|
71938
|
+
const v = run(`git config ${key}`, { encoding: "utf8", cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
71939
|
+
const t = String(v || "").trim();
|
|
71940
|
+
return t || null;
|
|
71941
|
+
} catch {
|
|
71942
|
+
return null;
|
|
71949
71943
|
}
|
|
71950
|
-
process.exit(code);
|
|
71951
71944
|
}
|
|
71952
|
-
|
|
71953
|
-
|
|
71954
|
-
|
|
71955
|
-
|
|
71956
|
-
|
|
71957
|
-
|
|
71958
|
-
|
|
71959
|
-
|
|
71960
|
-
renderDeployGateTerminal
|
|
71961
|
-
};
|
|
71962
|
-
}
|
|
71963
|
-
});
|
|
71964
|
-
|
|
71965
|
-
// src/commands/publish-gate.js
|
|
71966
|
-
var require_publish_gate = __commonJS({
|
|
71967
|
-
"src/commands/publish-gate.js"(exports2, module2) {
|
|
71968
|
-
"use strict";
|
|
71969
|
-
var fs = require("fs");
|
|
71970
|
-
var path = require("path");
|
|
71971
|
-
var { execFileSync } = require("child_process");
|
|
71972
|
-
var chalk = require_source();
|
|
71973
|
-
var { getApiKey } = require_config();
|
|
71974
|
-
var { cloudDiff } = require_cloud();
|
|
71975
|
-
if (process.env.NO_COLOR) chalk.level = 0;
|
|
71976
|
-
var ZERO_SHA = "0000000000000000000000000000000000000000";
|
|
71977
|
-
var CLOSED_ACTIONS = /* @__PURE__ */ new Set([
|
|
71978
|
-
"CONTINUE",
|
|
71979
|
-
"CONTINUE_WITH_MONITORING",
|
|
71980
|
-
"REQUEST_APPROVAL",
|
|
71981
|
-
"STOP"
|
|
71982
|
-
]);
|
|
71983
|
-
var PERMIT_ACTIONS = /* @__PURE__ */ new Set(["CONTINUE", "CONTINUE_WITH_MONITORING"]);
|
|
71984
|
-
function defaultGit(args, cwd) {
|
|
71985
|
-
return execFileSync("git", args, {
|
|
71986
|
-
cwd: cwd || process.cwd(),
|
|
71987
|
-
encoding: "utf8",
|
|
71988
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
71989
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
71990
|
-
}).trim();
|
|
71945
|
+
function resolveApiKey(deps = {}) {
|
|
71946
|
+
const getKey = deps.getApiKey || getApiKey;
|
|
71947
|
+
const fromLogin = getKey();
|
|
71948
|
+
if (fromLogin) return fromLogin;
|
|
71949
|
+
const env = deps.env || process.env;
|
|
71950
|
+
const fromEnv = env.CODERIFTS_API_KEY && String(env.CODERIFTS_API_KEY).trim();
|
|
71951
|
+
if (fromEnv) return fromEnv;
|
|
71952
|
+
return readGitConfig("coderifts.apiKey", deps);
|
|
71991
71953
|
}
|
|
71992
|
-
function
|
|
71993
|
-
|
|
71994
|
-
|
|
71995
|
-
|
|
71996
|
-
} catch (err) {
|
|
71997
|
-
const msg = err && err.stderr ? String(err.stderr) : err && err.message || String(err);
|
|
71998
|
-
return {
|
|
71999
|
-
ok: false,
|
|
72000
|
-
code: "GIT_ERROR",
|
|
72001
|
-
message: `git error reading ${ref}:${filePath}: ${msg.slice(0, 300)}`
|
|
72002
|
-
};
|
|
72003
|
-
}
|
|
71954
|
+
function resolveSpecPath(deps = {}) {
|
|
71955
|
+
const fromGit = readGitConfig("coderifts.specPath", deps);
|
|
71956
|
+
if (fromGit) return fromGit;
|
|
71957
|
+
return DEFAULT_SPEC_PATH;
|
|
72004
71958
|
}
|
|
72005
|
-
function
|
|
72006
|
-
|
|
71959
|
+
function parseStdinJson(raw) {
|
|
71960
|
+
if (raw == null || String(raw).trim() === "") {
|
|
71961
|
+
return { ok: false, reason: "empty stdin" };
|
|
71962
|
+
}
|
|
71963
|
+
let obj;
|
|
72007
71964
|
try {
|
|
72008
|
-
|
|
72009
|
-
const pkg2 = JSON.parse(raw);
|
|
72010
|
-
return pkg2 && typeof pkg2.version === "string" ? pkg2.version : null;
|
|
71965
|
+
obj = JSON.parse(String(raw));
|
|
72011
71966
|
} catch {
|
|
72012
|
-
return
|
|
71967
|
+
return { ok: false, reason: "stdin is not JSON" };
|
|
72013
71968
|
}
|
|
71969
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
|
|
71970
|
+
return { ok: false, reason: "stdin JSON is not an object" };
|
|
71971
|
+
}
|
|
71972
|
+
const toolName = obj.tool_name || obj.toolName || obj.name || null;
|
|
71973
|
+
let toolInput = obj.tool_input || obj.toolInput || obj.input || null;
|
|
71974
|
+
if (toolInput == null && obj.file_path) toolInput = obj;
|
|
71975
|
+
if (!toolName || typeof toolName !== "string") {
|
|
71976
|
+
return { ok: false, reason: "missing tool_name" };
|
|
71977
|
+
}
|
|
71978
|
+
if (!toolInput || typeof toolInput !== "object") {
|
|
71979
|
+
return { ok: false, reason: "missing tool_input" };
|
|
71980
|
+
}
|
|
71981
|
+
return { ok: true, toolName: String(toolName), toolInput };
|
|
72014
71982
|
}
|
|
72015
|
-
function
|
|
72016
|
-
|
|
72017
|
-
|
|
72018
|
-
|
|
72019
|
-
|
|
72020
|
-
|
|
72021
|
-
const
|
|
72022
|
-
const
|
|
72023
|
-
if (
|
|
72024
|
-
const
|
|
72025
|
-
|
|
72026
|
-
tried.push(`tag:${tag}`);
|
|
72027
|
-
try {
|
|
72028
|
-
gitImpl(["rev-parse", "--verify", `${tag}^{commit}`], cwd);
|
|
72029
|
-
} catch {
|
|
72030
|
-
continue;
|
|
72031
|
-
}
|
|
72032
|
-
const shown = gitShow(tag, specPath, { gitImpl, cwd });
|
|
72033
|
-
if (!shown.ok) {
|
|
72034
|
-
return {
|
|
72035
|
-
ok: false,
|
|
72036
|
-
code: shown.code || "GIT_ERROR",
|
|
72037
|
-
message: shown.message,
|
|
72038
|
-
tried
|
|
72039
|
-
};
|
|
72040
|
-
}
|
|
72041
|
-
if (shown.content.trim() === "") {
|
|
72042
|
-
return {
|
|
72043
|
-
ok: false,
|
|
72044
|
-
code: "EMPTY_BEFORE",
|
|
72045
|
-
message: `Empty contract artifact at tag ${tag}:${specPath}. Refusing to treat empty before as NEW_ARTIFACT (would false-pass enforce).`,
|
|
72046
|
-
tried
|
|
72047
|
-
};
|
|
72048
|
-
}
|
|
72049
|
-
return { ok: true, content: shown.content, source: `tag:${tag}`, tried };
|
|
72050
|
-
}
|
|
72051
|
-
} else {
|
|
72052
|
-
tried.push("package.json:version (missing)");
|
|
71983
|
+
function isSpecPath(filePath, specPath) {
|
|
71984
|
+
if (!filePath || !specPath) return false;
|
|
71985
|
+
const fp = path.normalize(String(filePath).replace(/\\/g, "/"));
|
|
71986
|
+
const sp = path.normalize(String(specPath).replace(/\\/g, "/"));
|
|
71987
|
+
if (fp === sp) return true;
|
|
71988
|
+
if (fp.endsWith("/" + sp) || fp.endsWith(sp)) return true;
|
|
71989
|
+
const baseFp = path.basename(fp);
|
|
71990
|
+
const baseSp = path.basename(sp);
|
|
71991
|
+
if (baseFp === baseSp && (fp.endsWith(sp) || sp.endsWith(baseSp))) {
|
|
71992
|
+
const tail = sp.split("/").filter(Boolean).join("/");
|
|
71993
|
+
return fp.replace(/\\/g, "/").endsWith(tail);
|
|
72053
71994
|
}
|
|
72054
|
-
|
|
72055
|
-
|
|
72056
|
-
|
|
72057
|
-
|
|
72058
|
-
|
|
72059
|
-
|
|
72060
|
-
|
|
72061
|
-
continue;
|
|
71995
|
+
return false;
|
|
71996
|
+
}
|
|
71997
|
+
function deriveAfterContent(toolName, toolInput, diskBefore) {
|
|
71998
|
+
const name = String(toolName || "");
|
|
71999
|
+
if (name === "Write" || name === "write") {
|
|
72000
|
+
if (typeof toolInput.content !== "string") {
|
|
72001
|
+
return { ok: false, reason: "Write tool_input.content missing or not a string" };
|
|
72062
72002
|
}
|
|
72063
|
-
|
|
72064
|
-
|
|
72065
|
-
|
|
72066
|
-
|
|
72067
|
-
|
|
72068
|
-
|
|
72069
|
-
|
|
72070
|
-
tried
|
|
72071
|
-
};
|
|
72003
|
+
return { ok: true, after: toolInput.content };
|
|
72004
|
+
}
|
|
72005
|
+
if (name === "Edit" || name === "edit") {
|
|
72006
|
+
const oldS = toolInput.old_string != null ? toolInput.old_string : toolInput.oldString;
|
|
72007
|
+
const newS = toolInput.new_string != null ? toolInput.new_string : toolInput.newString;
|
|
72008
|
+
if (typeof oldS !== "string" || typeof newS !== "string") {
|
|
72009
|
+
return { ok: false, reason: "Edit tool_input.old_string/new_string missing" };
|
|
72072
72010
|
}
|
|
72073
|
-
if (
|
|
72074
|
-
return {
|
|
72075
|
-
ok: false,
|
|
72076
|
-
code: "EMPTY_BEFORE",
|
|
72077
|
-
message: `Empty contract artifact at merge-base ${mb}:${specPath} (${base}). Refusing to treat empty before as NEW_ARTIFACT (would false-pass enforce).`,
|
|
72078
|
-
tried
|
|
72079
|
-
};
|
|
72011
|
+
if (!diskBefore.includes(oldS)) {
|
|
72012
|
+
return { ok: false, reason: "Edit old_string not found in disk content" };
|
|
72080
72013
|
}
|
|
72081
|
-
return {
|
|
72082
|
-
ok: true,
|
|
72083
|
-
content: shown.content,
|
|
72084
|
-
source: `merge-base:${base}@${mb.slice(0, 12)}`,
|
|
72085
|
-
tried
|
|
72086
|
-
};
|
|
72014
|
+
return { ok: true, after: diskBefore.replace(oldS, newS) };
|
|
72087
72015
|
}
|
|
72088
|
-
|
|
72089
|
-
|
|
72090
|
-
|
|
72091
|
-
|
|
72092
|
-
tried
|
|
72093
|
-
};
|
|
72094
|
-
}
|
|
72095
|
-
function resolveAfterSpec(specPath, {
|
|
72096
|
-
cwd = process.cwd(),
|
|
72097
|
-
readFile = fs.readFileSync,
|
|
72098
|
-
exists = fs.existsSync
|
|
72099
|
-
} = {}) {
|
|
72100
|
-
const resolved = path.isAbsolute(specPath) ? specPath : path.join(cwd, specPath);
|
|
72101
|
-
try {
|
|
72102
|
-
if (!exists(resolved)) {
|
|
72103
|
-
return {
|
|
72104
|
-
ok: false,
|
|
72105
|
-
code: "AFTER_MISSING",
|
|
72106
|
-
message: `Working-tree contract artifact not found: ${specPath}`
|
|
72107
|
-
};
|
|
72016
|
+
if (name === "MultiEdit" || name === "multi_edit" || name === "multiEdit") {
|
|
72017
|
+
const edits = toolInput.edits || toolInput.Edits;
|
|
72018
|
+
if (!Array.isArray(edits) || edits.length === 0) {
|
|
72019
|
+
return { ok: false, reason: "MultiEdit tool_input.edits missing or empty" };
|
|
72108
72020
|
}
|
|
72109
|
-
|
|
72110
|
-
|
|
72111
|
-
|
|
72112
|
-
|
|
72113
|
-
|
|
72114
|
-
|
|
72115
|
-
|
|
72021
|
+
let cur = diskBefore;
|
|
72022
|
+
for (let i = 0; i < edits.length; i++) {
|
|
72023
|
+
const e = edits[i] || {};
|
|
72024
|
+
const oldS = e.old_string != null ? e.old_string : e.oldString;
|
|
72025
|
+
const newS = e.new_string != null ? e.new_string : e.newString;
|
|
72026
|
+
if (typeof oldS !== "string" || typeof newS !== "string") {
|
|
72027
|
+
return { ok: false, reason: `MultiEdit edits[${i}] missing old_string/new_string` };
|
|
72028
|
+
}
|
|
72029
|
+
if (!cur.includes(oldS)) {
|
|
72030
|
+
return { ok: false, reason: `MultiEdit edits[${i}] old_string not found in content` };
|
|
72031
|
+
}
|
|
72032
|
+
cur = cur.replace(oldS, newS);
|
|
72116
72033
|
}
|
|
72117
|
-
return { ok: true,
|
|
72118
|
-
} catch (err) {
|
|
72119
|
-
return {
|
|
72120
|
-
ok: false,
|
|
72121
|
-
code: "AFTER_READ_ERROR",
|
|
72122
|
-
message: `Failed to read working-tree ${specPath}: ${err && err.message}`
|
|
72123
|
-
};
|
|
72034
|
+
return { ok: true, after: cur };
|
|
72124
72035
|
}
|
|
72036
|
+
return { ok: false, reason: `unsupported tool_name for content derive: ${name}` };
|
|
72125
72037
|
}
|
|
72126
|
-
function
|
|
72127
|
-
if (
|
|
72128
|
-
|
|
72129
|
-
|
|
72130
|
-
|
|
72131
|
-
|
|
72132
|
-
|
|
72133
|
-
|
|
72134
|
-
|
|
72135
|
-
|
|
72038
|
+
function isV2DecisionBody(d, dr) {
|
|
72039
|
+
if (dr && typeof dr === "object") return true;
|
|
72040
|
+
if (d.preflight_mode != null && d.preflight_mode !== "") return true;
|
|
72041
|
+
const ver = d.decision_spec_version;
|
|
72042
|
+
return typeof ver === "string" && ver.startsWith("2.");
|
|
72043
|
+
}
|
|
72044
|
+
function allowLegacyDecisionMap(d, dr) {
|
|
72045
|
+
return d.decision_spec_version === "1.0" && !isV2DecisionBody(d, dr);
|
|
72046
|
+
}
|
|
72047
|
+
function mapDecisionSeverity(result) {
|
|
72048
|
+
const d = result && typeof result === "object" && !Array.isArray(result) ? result : {};
|
|
72136
72049
|
let ea = null;
|
|
72137
|
-
|
|
72138
|
-
|
|
72139
|
-
|
|
72140
|
-
if (
|
|
72141
|
-
|
|
72142
|
-
return {
|
|
72143
|
-
allow,
|
|
72144
|
-
execution_action: ea,
|
|
72145
|
-
decision: decision || null,
|
|
72146
|
-
policy: allow ? `permit:execution_action=${ea}` : `block:execution_action=${ea}`
|
|
72147
|
-
};
|
|
72148
|
-
}
|
|
72149
|
-
if (ea != null && ea !== "" && !CLOSED_ACTIONS.has(ea)) {
|
|
72150
|
-
return {
|
|
72151
|
-
allow: false,
|
|
72152
|
-
execution_action: ea,
|
|
72153
|
-
decision: decision || null,
|
|
72154
|
-
policy: `block:unrecognised_execution_action=${ea}`
|
|
72155
|
-
};
|
|
72156
|
-
}
|
|
72157
|
-
if (decision === "BLOCK" || decision === "REQUIRE_APPROVAL") {
|
|
72158
|
-
return {
|
|
72159
|
-
allow: false,
|
|
72160
|
-
execution_action: null,
|
|
72161
|
-
decision,
|
|
72162
|
-
policy: `block:decision=${decision}`
|
|
72163
|
-
};
|
|
72050
|
+
const dr = d.decision_result;
|
|
72051
|
+
if (dr && typeof dr === "object" && typeof dr.execution_action === "string" && dr.execution_action !== "") {
|
|
72052
|
+
ea = dr.execution_action;
|
|
72053
|
+
} else if (typeof d.execution_action === "string" && d.execution_action !== "") {
|
|
72054
|
+
ea = d.execution_action;
|
|
72164
72055
|
}
|
|
72165
|
-
|
|
72056
|
+
const hasDecision = d.omega_decision != null && d.omega_decision !== "" || d.decision != null && d.decision !== "" || dr && typeof dr === "object" && dr.execution_action;
|
|
72057
|
+
if (d.error && !hasDecision && (ea == null || ea === "")) {
|
|
72166
72058
|
return {
|
|
72167
|
-
|
|
72168
|
-
|
|
72169
|
-
|
|
72170
|
-
|
|
72059
|
+
severity: "INDETERMINATE",
|
|
72060
|
+
decision: null,
|
|
72061
|
+
decisionId: null,
|
|
72062
|
+
executionAction: null
|
|
72171
72063
|
};
|
|
72172
72064
|
}
|
|
72173
|
-
|
|
72174
|
-
if (
|
|
72175
|
-
|
|
72176
|
-
|
|
72177
|
-
|
|
72178
|
-
|
|
72179
|
-
|
|
72180
|
-
|
|
72065
|
+
let severity;
|
|
72066
|
+
if (ea != null && ea !== "") {
|
|
72067
|
+
if (!CLOSED_ACTIONS.has(ea)) {
|
|
72068
|
+
severity = "UNKNOWN";
|
|
72069
|
+
} else if (ea === "CONTINUE") {
|
|
72070
|
+
severity = "ALLOW";
|
|
72071
|
+
} else if (ea === "CONTINUE_WITH_MONITORING") {
|
|
72072
|
+
severity = "MONITOR";
|
|
72073
|
+
} else if (ea === "STOP") {
|
|
72074
|
+
severity = "BLOCK";
|
|
72075
|
+
} else {
|
|
72076
|
+
severity = "REQUIRE_APPROVAL";
|
|
72077
|
+
}
|
|
72078
|
+
} else if (allowLegacyDecisionMap(d, dr)) {
|
|
72079
|
+
const od = d.omega_decision || d.decision;
|
|
72080
|
+
if (od == null || od === "") {
|
|
72081
|
+
severity = "INDETERMINATE";
|
|
72082
|
+
} else if (od === "BLOCK") severity = "BLOCK";
|
|
72083
|
+
else if (od === "REQUIRE_APPROVAL") severity = "REQUIRE_APPROVAL";
|
|
72084
|
+
else if (od === "WARN") severity = "WARN";
|
|
72085
|
+
else severity = "ALLOW";
|
|
72086
|
+
} else {
|
|
72087
|
+
severity = "INDETERMINATE";
|
|
72181
72088
|
}
|
|
72089
|
+
const decision = dr && dr.decision || d.decision || d.omega_decision || null;
|
|
72090
|
+
const decisionId = dr && dr.decision_id || d.decision_id || null;
|
|
72182
72091
|
return {
|
|
72183
|
-
|
|
72184
|
-
|
|
72185
|
-
|
|
72186
|
-
|
|
72092
|
+
severity,
|
|
72093
|
+
decision: decision != null && decision !== "" ? String(decision) : null,
|
|
72094
|
+
decisionId: decisionId != null ? String(decisionId) : null,
|
|
72095
|
+
executionAction: ea
|
|
72187
72096
|
};
|
|
72188
72097
|
}
|
|
72189
|
-
function
|
|
72190
|
-
|
|
72191
|
-
const
|
|
72192
|
-
|
|
72193
|
-
|
|
72194
|
-
|
|
72195
|
-
|
|
72196
|
-
|
|
72197
|
-
|
|
72198
|
-
|
|
72199
|
-
|
|
72200
|
-
|
|
72201
|
-
|
|
72202
|
-
|
|
72203
|
-
|
|
72204
|
-
|
|
72098
|
+
function renderDecisionWhy(result, opts = {}) {
|
|
72099
|
+
const maxFixes = Number.isInteger(opts.maxFixes) ? opts.maxFixes : 5;
|
|
72100
|
+
const maxLines = Number.isInteger(opts.maxLines) ? opts.maxLines : 13;
|
|
72101
|
+
const d = result && typeof result === "object" && !Array.isArray(result) ? result : {};
|
|
72102
|
+
const dr = d.decision_result && typeof d.decision_result === "object" ? d.decision_result : {};
|
|
72103
|
+
const pick = (k) => dr[k] !== void 0 ? dr[k] : d[k];
|
|
72104
|
+
const arr = (v) => Array.isArray(v) ? v : [];
|
|
72105
|
+
const str = (v) => typeof v === "string" ? v.trim() : "";
|
|
72106
|
+
const lines = [];
|
|
72107
|
+
const reasonCodes = [];
|
|
72108
|
+
for (const r of arr(pick("blocking_reasons"))) {
|
|
72109
|
+
if (!r || typeof r !== "object") continue;
|
|
72110
|
+
const code = str(r.code);
|
|
72111
|
+
const message = str(r.message);
|
|
72112
|
+
if (code) reasonCodes.push(code);
|
|
72113
|
+
if (!code && !message) continue;
|
|
72114
|
+
lines.push(`- ${code || "REASON"}${message ? `: ${message}` : ""}`);
|
|
72205
72115
|
}
|
|
72206
|
-
const
|
|
72207
|
-
|
|
72208
|
-
|
|
72209
|
-
|
|
72210
|
-
|
|
72211
|
-
|
|
72212
|
-
newSpec = yaml.load(after);
|
|
72213
|
-
} catch (e) {
|
|
72214
|
-
const err = new Error(`Failed to parse specs: ${e.message}`);
|
|
72215
|
-
err.code = "PREFLIGHT_UNREACHABLE";
|
|
72216
|
-
throw err;
|
|
72116
|
+
for (const r of arr(pick("degraded_reasons"))) {
|
|
72117
|
+
if (!r || typeof r !== "object") continue;
|
|
72118
|
+
const code = str(r.code);
|
|
72119
|
+
const message = str(r.message);
|
|
72120
|
+
if (!code && !message) continue;
|
|
72121
|
+
lines.push(`- degraded: ${[code, message].filter(Boolean).join(": ")}`);
|
|
72217
72122
|
}
|
|
72218
|
-
|
|
72219
|
-
|
|
72220
|
-
|
|
72221
|
-
|
|
72222
|
-
|
|
72223
|
-
|
|
72224
|
-
|
|
72225
|
-
const
|
|
72226
|
-
|
|
72227
|
-
|
|
72123
|
+
const action = str(pick("required_action"));
|
|
72124
|
+
if (action) lines.push(`- action: ${action}`);
|
|
72125
|
+
const rt = pick("remediation_transaction");
|
|
72126
|
+
const changes = arr(rt && typeof rt === "object" ? rt.required_changes : null).filter((c) => c && typeof c === "object");
|
|
72127
|
+
const shown = changes.slice(0, maxFixes);
|
|
72128
|
+
for (const c of shown) {
|
|
72129
|
+
const target = str(c.target);
|
|
72130
|
+
const instruction = str(c.instruction);
|
|
72131
|
+
if (!instruction && !target) continue;
|
|
72132
|
+
lines.push(`- fix${target ? ` (${target})` : ""}: ${instruction || str(c.precise_label)}`);
|
|
72228
72133
|
}
|
|
72229
|
-
|
|
72230
|
-
|
|
72231
|
-
const execution_action = breaking > 0 ? "STOP" : "CONTINUE";
|
|
72232
|
-
return {
|
|
72233
|
-
decision,
|
|
72234
|
-
omega_decision: decision,
|
|
72235
|
-
execution_action,
|
|
72236
|
-
decision_result: {
|
|
72237
|
-
decision,
|
|
72238
|
-
execution_action,
|
|
72239
|
-
decision_id: `local-${Date.now()}`
|
|
72240
|
-
},
|
|
72241
|
-
breaking_changes: diffResult.breakingDifferences || [],
|
|
72242
|
-
risk_score: Math.min(breaking * 15, 100)
|
|
72243
|
-
};
|
|
72134
|
+
if (changes.length > shown.length) lines.push(`- fix: +${changes.length - shown.length} more`);
|
|
72135
|
+
return { lines: lines.slice(0, maxLines), reasonCodes };
|
|
72244
72136
|
}
|
|
72245
|
-
async function
|
|
72246
|
-
const
|
|
72247
|
-
const
|
|
72248
|
-
const readFile = deps.readFile || fs.readFileSync.bind(fs);
|
|
72249
|
-
const exists = deps.exists || fs.existsSync.bind(fs);
|
|
72250
|
-
const preflightFn = deps.preflightFn || defaultPreflight;
|
|
72251
|
-
const log = deps.log || console.log.bind(console);
|
|
72252
|
-
const logErr = deps.logErr || console.error.bind(console);
|
|
72253
|
-
let specPath = options.spec;
|
|
72254
|
-
if (!specPath) {
|
|
72137
|
+
async function runClaudeHook(options = {}, deps = {}) {
|
|
72138
|
+
const errLog = deps.errLog || ((m) => console.error(String(m)));
|
|
72139
|
+
const readStdin = deps.readStdin || (() => {
|
|
72255
72140
|
try {
|
|
72256
|
-
|
|
72141
|
+
return fs.readFileSync(0, "utf8");
|
|
72257
72142
|
} catch {
|
|
72258
|
-
|
|
72143
|
+
return "";
|
|
72259
72144
|
}
|
|
72260
|
-
}
|
|
72261
|
-
if (!specPath) specPath = "api/openapi.yaml";
|
|
72262
|
-
try {
|
|
72263
|
-
gitImpl(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
72264
|
-
} catch (err) {
|
|
72265
|
-
const msg = "CodeRifts publish-gate: GIT_ERROR \u2014 not a git repository (or git unavailable). Cannot resolve before-spec without git. Fail-closed.";
|
|
72266
|
-
logErr(chalk.red(msg));
|
|
72267
|
-
return finish({
|
|
72268
|
-
ok: false,
|
|
72269
|
-
exitCode: 1,
|
|
72270
|
-
code: "GIT_ERROR",
|
|
72271
|
-
message: msg,
|
|
72272
|
-
policy: "fail_closed:git_error"
|
|
72273
|
-
}, { log, json: options.json });
|
|
72274
|
-
}
|
|
72275
|
-
const beforeRes = resolveBeforeSpec(specPath, {
|
|
72276
|
-
gitImpl,
|
|
72277
|
-
cwd,
|
|
72278
|
-
readFile,
|
|
72279
|
-
packageVersion: deps.packageVersion
|
|
72280
72145
|
});
|
|
72281
|
-
|
|
72282
|
-
|
|
72283
|
-
|
|
72284
|
-
|
|
72285
|
-
|
|
72286
|
-
|
|
72287
|
-
|
|
72288
|
-
|
|
72289
|
-
|
|
72290
|
-
|
|
72291
|
-
|
|
72292
|
-
|
|
72293
|
-
|
|
72294
|
-
|
|
72295
|
-
|
|
72296
|
-
|
|
72297
|
-
|
|
72298
|
-
|
|
72299
|
-
|
|
72300
|
-
|
|
72301
|
-
|
|
72302
|
-
|
|
72303
|
-
|
|
72304
|
-
|
|
72305
|
-
ok: true,
|
|
72306
|
-
exitCode: 0,
|
|
72307
|
-
code: "UNCHANGED",
|
|
72308
|
-
message: "Contract artifact unchanged vs baseline; publish permitted.",
|
|
72309
|
-
policy: "permit:unchanged",
|
|
72310
|
-
before_source: beforeRes.source,
|
|
72311
|
-
execution_action: "CONTINUE",
|
|
72312
|
-
receipt: null
|
|
72313
|
-
};
|
|
72314
|
-
if (!options.json) {
|
|
72315
|
-
log(chalk.green("CodeRifts publish-gate: ALLOW (unchanged)"));
|
|
72316
|
-
log(` baseline: ${beforeRes.source}`);
|
|
72317
|
-
log(` spec: ${specPath}`);
|
|
72318
|
-
}
|
|
72319
|
-
return finish(payload2, { log, json: options.json });
|
|
72320
|
-
}
|
|
72321
|
-
let result;
|
|
72322
|
-
try {
|
|
72323
|
-
result = await preflightFn(beforeRes.content, afterRes.content, {
|
|
72324
|
-
apiKey: deps.apiKey,
|
|
72325
|
-
cwd
|
|
72326
|
-
});
|
|
72327
|
-
} catch (err) {
|
|
72328
|
-
const msg = `CodeRifts publish-gate: PREFLIGHT_UNREACHABLE \u2014 ${err && err.message}`;
|
|
72329
|
-
logErr(chalk.red(msg));
|
|
72330
|
-
return finish({
|
|
72331
|
-
ok: false,
|
|
72332
|
-
exitCode: 1,
|
|
72333
|
-
code: "PREFLIGHT_UNREACHABLE",
|
|
72334
|
-
message: msg,
|
|
72335
|
-
policy: "fail_closed:preflight_unreachable"
|
|
72336
|
-
}, { log, json: options.json });
|
|
72337
|
-
}
|
|
72338
|
-
const perm = evaluatePublishPermission(result);
|
|
72339
|
-
const receipt = extractReceiptRef(result);
|
|
72340
|
-
if (!perm.allow) {
|
|
72341
|
-
const payload2 = {
|
|
72342
|
-
ok: false,
|
|
72343
|
-
exitCode: 1,
|
|
72344
|
-
code: "BLOCK",
|
|
72345
|
-
message: "Publish not permitted by execution_action / decision.",
|
|
72346
|
-
policy: perm.policy,
|
|
72347
|
-
execution_action: perm.execution_action,
|
|
72348
|
-
decision: perm.decision,
|
|
72349
|
-
before_source: beforeRes.source,
|
|
72350
|
-
receipt,
|
|
72351
|
-
fail_policy: "exit_1_on_block_or_resolver_error_or_unreachable"
|
|
72146
|
+
const readFile = deps.readFile || ((p) => fs.readFileSync(p, "utf8"));
|
|
72147
|
+
const exists = deps.exists || ((p) => fs.existsSync(p));
|
|
72148
|
+
const authorize = deps.cloudAuthorizePreflight || cloudAuthorizePreflight;
|
|
72149
|
+
const cwd = deps.cwd || process.cwd();
|
|
72150
|
+
const env = deps.env || process.env;
|
|
72151
|
+
const strict = isStrictMode(env);
|
|
72152
|
+
const advisory = isAdvisoryMode(env);
|
|
72153
|
+
const sessionId = hookSessionId(env);
|
|
72154
|
+
const logEv = (partial) => {
|
|
72155
|
+
appendGuardEventLog(env, {
|
|
72156
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
72157
|
+
sessionId,
|
|
72158
|
+
trigger_source: HOOK_TRIGGER_SOURCE,
|
|
72159
|
+
...partial
|
|
72160
|
+
}, { errLog, appendFileSync: deps.appendFileSync });
|
|
72161
|
+
};
|
|
72162
|
+
let tool = null;
|
|
72163
|
+
let filePathKnown = null;
|
|
72164
|
+
const emitTerminal = (r) => {
|
|
72165
|
+
const extra = {
|
|
72166
|
+
tool: tool || void 0,
|
|
72167
|
+
path: filePathKnown || void 0,
|
|
72168
|
+
decision: r.decision != null ? r.decision : void 0,
|
|
72169
|
+
decisionId: r.decisionId != null ? r.decisionId : void 0
|
|
72352
72170
|
};
|
|
72353
|
-
if (
|
|
72354
|
-
|
|
72355
|
-
|
|
72356
|
-
|
|
72357
|
-
|
|
72358
|
-
|
|
72359
|
-
|
|
72360
|
-
|
|
72361
|
-
|
|
72362
|
-
|
|
72363
|
-
|
|
72364
|
-
|
|
72171
|
+
if (r.exitCode === 2) {
|
|
72172
|
+
const reasons = Array.isArray(r.reasons) && r.reasons.length ? r.reasons : void 0;
|
|
72173
|
+
logEv({
|
|
72174
|
+
type: "hook_blocked",
|
|
72175
|
+
exit: 2,
|
|
72176
|
+
...extra,
|
|
72177
|
+
reasons,
|
|
72178
|
+
cause: r.reason || r.site
|
|
72179
|
+
});
|
|
72180
|
+
} else if (r.exitCode === 0 && r.site && advisory && !strict) {
|
|
72181
|
+
logEv({ type: "hook_advisory_passthrough", exit: 0, decision: r.decision || null, ...extra, cause: r.site });
|
|
72182
|
+
} else {
|
|
72183
|
+
const proof = r.reason === "allow" ? {
|
|
72184
|
+
decision_id: r.decisionId != null ? r.decisionId : extra.decisionId || null,
|
|
72185
|
+
fp_prefix: fingerprintPrefix(r.fingerprint) || null
|
|
72186
|
+
} : void 0;
|
|
72187
|
+
logEv({
|
|
72188
|
+
type: "hook_continue",
|
|
72189
|
+
exit: 0,
|
|
72190
|
+
...extra,
|
|
72191
|
+
cause: r.reason,
|
|
72192
|
+
...proof ? { proof } : {}
|
|
72193
|
+
});
|
|
72365
72194
|
}
|
|
72366
|
-
return
|
|
72367
|
-
}
|
|
72368
|
-
const payload = {
|
|
72369
|
-
ok: true,
|
|
72370
|
-
exitCode: 0,
|
|
72371
|
-
code: "ALLOW",
|
|
72372
|
-
message: "Publish permitted.",
|
|
72373
|
-
policy: perm.policy,
|
|
72374
|
-
execution_action: perm.execution_action,
|
|
72375
|
-
decision: perm.decision,
|
|
72376
|
-
before_source: beforeRes.source,
|
|
72377
|
-
receipt
|
|
72195
|
+
return r;
|
|
72378
72196
|
};
|
|
72379
|
-
|
|
72380
|
-
|
|
72381
|
-
|
|
72382
|
-
|
|
72383
|
-
|
|
72384
|
-
|
|
72385
|
-
|
|
72197
|
+
const apiKey = resolveApiKey({ ...deps, cwd, env });
|
|
72198
|
+
if (!apiKey) {
|
|
72199
|
+
logEv({ type: "preflight_unavailable", cause: "missing_api_key" });
|
|
72200
|
+
return emitTerminal(failClosedOrAdvisory({
|
|
72201
|
+
advisory,
|
|
72202
|
+
strict,
|
|
72203
|
+
site: "missing_api_key",
|
|
72204
|
+
why: "no API key",
|
|
72205
|
+
softMsg: "CodeRifts claude-hook: no API key (coderifts login / CODERIFTS_API_KEY / git config coderifts.apiKey) \u2014 allowing",
|
|
72206
|
+
errLog
|
|
72207
|
+
}));
|
|
72386
72208
|
}
|
|
72387
|
-
|
|
72388
|
-
|
|
72389
|
-
|
|
72390
|
-
if (
|
|
72391
|
-
|
|
72209
|
+
const PARSE_GAP_STDERR = "unparseable input \u2014 refusing (fail-closed); set CODERIFTS_ADVISORY=1 to soften";
|
|
72210
|
+
const raw = typeof options.stdin === "string" ? options.stdin : readStdin();
|
|
72211
|
+
const parsed = parseStdinJson(raw);
|
|
72212
|
+
if (!parsed.ok) {
|
|
72213
|
+
if (advisory && !strict) {
|
|
72214
|
+
errLog(`CodeRifts claude-hook: ${parsed.reason} \u2014 allowing (CODERIFTS_ADVISORY)`);
|
|
72215
|
+
return emitTerminal({ exitCode: 0, reason: "stdin_unparseable", site: "stdin_unparseable" });
|
|
72216
|
+
}
|
|
72217
|
+
errLog(`CodeRifts claude-hook: ${PARSE_GAP_STDERR}`);
|
|
72218
|
+
return emitTerminal({ exitCode: 2, reason: "stdin_unparseable" });
|
|
72392
72219
|
}
|
|
72393
|
-
|
|
72394
|
-
|
|
72395
|
-
|
|
72396
|
-
|
|
72397
|
-
|
|
72398
|
-
|
|
72399
|
-
|
|
72400
|
-
extractReceiptRef,
|
|
72401
|
-
gitShow,
|
|
72402
|
-
defaultGit,
|
|
72403
|
-
PERMIT_ACTIONS,
|
|
72404
|
-
CLOSED_ACTIONS
|
|
72405
|
-
};
|
|
72406
|
-
}
|
|
72407
|
-
});
|
|
72408
|
-
|
|
72409
|
-
// src/registry-validation-core.js
|
|
72410
|
-
var require_registry_validation_core = __commonJS({
|
|
72411
|
-
"src/registry-validation-core.js"(exports2, module2) {
|
|
72412
|
-
"use strict";
|
|
72413
|
-
var yaml = require_js_yaml();
|
|
72414
|
-
function safeParse(content) {
|
|
72415
|
-
if (!content) return null;
|
|
72416
|
-
try {
|
|
72417
|
-
const trimmed = content.trim();
|
|
72418
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
72419
|
-
return JSON.parse(trimmed);
|
|
72220
|
+
const { toolName, toolInput } = parsed;
|
|
72221
|
+
tool = toolName;
|
|
72222
|
+
const filePath = toolInput.file_path || toolInput.filePath || toolInput.path;
|
|
72223
|
+
if (!filePath || typeof filePath !== "string") {
|
|
72224
|
+
if (advisory && !strict) {
|
|
72225
|
+
errLog("CodeRifts claude-hook: tool_input.file_path missing \u2014 allowing (CODERIFTS_ADVISORY)");
|
|
72226
|
+
return emitTerminal({ exitCode: 0, reason: "missing_file_path", site: "missing_file_path" });
|
|
72420
72227
|
}
|
|
72421
|
-
|
|
72422
|
-
|
|
72423
|
-
return null;
|
|
72228
|
+
errLog(`CodeRifts claude-hook: ${PARSE_GAP_STDERR}`);
|
|
72229
|
+
return emitTerminal({ exitCode: 2, reason: "missing_file_path" });
|
|
72424
72230
|
}
|
|
72425
|
-
|
|
72426
|
-
|
|
72427
|
-
if (!
|
|
72428
|
-
|
|
72231
|
+
filePathKnown = filePath;
|
|
72232
|
+
const specPath = resolveSpecPath({ ...deps, cwd });
|
|
72233
|
+
if (!isSpecPath(filePath, specPath)) {
|
|
72234
|
+
logEv({ type: "detection_skip", signals: ["not_spec_path"], tool, path: filePath });
|
|
72235
|
+
return emitTerminal({ exitCode: 0, reason: "not_spec_path" });
|
|
72429
72236
|
}
|
|
72430
|
-
|
|
72431
|
-
|
|
72432
|
-
|
|
72433
|
-
|
|
72237
|
+
const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
|
|
72238
|
+
let diskBefore = "";
|
|
72239
|
+
if (exists(absPath)) {
|
|
72240
|
+
try {
|
|
72241
|
+
diskBefore = readFile(absPath, "utf8");
|
|
72242
|
+
} catch (e) {
|
|
72243
|
+
logEv({ type: "preflight_unavailable", cause: "disk_unreadable", tool, path: filePath });
|
|
72244
|
+
return emitTerminal(failClosedOrAdvisory({
|
|
72245
|
+
advisory,
|
|
72246
|
+
strict,
|
|
72247
|
+
site: "disk_unreadable",
|
|
72248
|
+
why: `cannot read contract file ${absPath}`,
|
|
72249
|
+
softMsg: `CodeRifts claude-hook: cannot read ${absPath} \u2014 allowing (soft)`,
|
|
72250
|
+
errLog
|
|
72251
|
+
}));
|
|
72434
72252
|
}
|
|
72435
|
-
return { valid: false, error: `Unsupported OpenAPI version: ${ver}` };
|
|
72436
72253
|
}
|
|
72437
|
-
|
|
72438
|
-
|
|
72439
|
-
|
|
72440
|
-
|
|
72441
|
-
|
|
72442
|
-
|
|
72254
|
+
const derived = deriveAfterContent(toolName, toolInput, diskBefore);
|
|
72255
|
+
if (!derived.ok) {
|
|
72256
|
+
logEv({ type: "preflight_unavailable", cause: "edit_apply_failed", tool, path: filePath });
|
|
72257
|
+
return emitTerminal(failClosedOrAdvisory({
|
|
72258
|
+
advisory,
|
|
72259
|
+
strict,
|
|
72260
|
+
site: "edit_apply_failed",
|
|
72261
|
+
why: derived.reason,
|
|
72262
|
+
softMsg: `CodeRifts claude-hook: ${derived.reason} \u2014 allowing (soft; never guess edit apply)`,
|
|
72263
|
+
errLog
|
|
72264
|
+
}));
|
|
72443
72265
|
}
|
|
72444
|
-
|
|
72445
|
-
|
|
72446
|
-
|
|
72447
|
-
const endpoints = [];
|
|
72448
|
-
const paths = parsed.paths || {};
|
|
72449
|
-
const httpMethods = ["get", "post", "put", "patch", "delete", "head", "options"];
|
|
72450
|
-
for (const [path, pathItem] of Object.entries(paths)) {
|
|
72451
|
-
if (!pathItem || typeof pathItem !== "object") continue;
|
|
72452
|
-
for (const method of httpMethods) {
|
|
72453
|
-
if (pathItem[method]) {
|
|
72454
|
-
endpoints.push({
|
|
72455
|
-
path,
|
|
72456
|
-
method: method.toUpperCase(),
|
|
72457
|
-
operationId: pathItem[method].operationId || ""
|
|
72458
|
-
});
|
|
72459
|
-
}
|
|
72460
|
-
}
|
|
72266
|
+
if (diskBefore === derived.after) {
|
|
72267
|
+
logEv({ type: "detection_skip", signals: ["identical"], tool, path: filePath });
|
|
72268
|
+
return emitTerminal({ exitCode: 0, reason: "identical" });
|
|
72461
72269
|
}
|
|
72462
|
-
|
|
72463
|
-
|
|
72464
|
-
|
|
72465
|
-
|
|
72466
|
-
|
|
72467
|
-
|
|
72468
|
-
|
|
72469
|
-
|
|
72270
|
+
logEv({ type: "preflight_start", tool, path: filePath });
|
|
72271
|
+
let result;
|
|
72272
|
+
try {
|
|
72273
|
+
result = await authorize(diskBefore, derived.after, apiKey, {
|
|
72274
|
+
operation: "merge",
|
|
72275
|
+
environment: "staging"
|
|
72276
|
+
});
|
|
72277
|
+
} catch (e) {
|
|
72278
|
+
const msg = e && e.message ? String(e.message) : "request failed";
|
|
72279
|
+
logEv({ type: "preflight_unavailable", cause: "api_unreachable", tool, path: filePath });
|
|
72280
|
+
return emitTerminal(failClosedOrAdvisory({
|
|
72281
|
+
advisory,
|
|
72282
|
+
strict,
|
|
72283
|
+
site: "api_unreachable",
|
|
72284
|
+
why: `API unreachable: ${msg}`,
|
|
72285
|
+
softMsg: `CodeRifts claude-hook: API unreachable (${msg}) \u2014 allowing (soft; availability must not brick the editor)`,
|
|
72286
|
+
errLog
|
|
72287
|
+
}));
|
|
72470
72288
|
}
|
|
72471
|
-
|
|
72472
|
-
|
|
72473
|
-
|
|
72474
|
-
const
|
|
72475
|
-
const
|
|
72476
|
-
|
|
72477
|
-
|
|
72478
|
-
|
|
72479
|
-
|
|
72480
|
-
|
|
72481
|
-
|
|
72482
|
-
|
|
72483
|
-
|
|
72484
|
-
|
|
72485
|
-
|
|
72289
|
+
const mapped = mapDecisionSeverity(result);
|
|
72290
|
+
const fingerprint = extractFingerprint(result);
|
|
72291
|
+
const idPart = mapped.decisionId ? ` decision_id=${mapped.decisionId}` : "";
|
|
72292
|
+
const decPart = mapped.decision ? ` decision=${mapped.decision}` : "";
|
|
72293
|
+
const eaPart = mapped.executionAction ? ` execution_action=${mapped.executionAction}` : "";
|
|
72294
|
+
const actionForLog = mapped.executionAction || (mapped.severity === "BLOCK" || mapped.severity === "UNKNOWN" || mapped.severity === "INDETERMINATE" ? "STOP" : mapped.severity === "REQUIRE_APPROVAL" ? "REQUEST_APPROVAL" : mapped.severity === "MONITOR" || mapped.severity === "WARN" ? "CONTINUE_WITH_MONITORING" : "CONTINUE");
|
|
72295
|
+
logEv({
|
|
72296
|
+
type: "preflight_result",
|
|
72297
|
+
action: actionForLog,
|
|
72298
|
+
decisionId: mapped.decisionId || void 0,
|
|
72299
|
+
fingerprint,
|
|
72300
|
+
tool,
|
|
72301
|
+
path: filePath
|
|
72302
|
+
});
|
|
72303
|
+
if (mapped.severity === "INDETERMINATE") {
|
|
72304
|
+
errLog(
|
|
72305
|
+
"CodeRifts claude-hook: BLOCKED (indeterminate response \u2014 no execution_action and no decision; not permission)"
|
|
72306
|
+
);
|
|
72307
|
+
return emitTerminal({
|
|
72308
|
+
exitCode: 2,
|
|
72309
|
+
reason: "indeterminate",
|
|
72310
|
+
severity: "INDETERMINATE",
|
|
72311
|
+
decision: mapped.decision,
|
|
72312
|
+
decisionId: mapped.decisionId
|
|
72313
|
+
});
|
|
72486
72314
|
}
|
|
72487
|
-
|
|
72488
|
-
|
|
72489
|
-
|
|
72490
|
-
|
|
72491
|
-
|
|
72492
|
-
|
|
72493
|
-
|
|
72494
|
-
|
|
72495
|
-
|
|
72496
|
-
|
|
72497
|
-
|
|
72498
|
-
|
|
72315
|
+
if (mapped.severity === "BLOCK" || mapped.severity === "UNKNOWN") {
|
|
72316
|
+
const why = renderDecisionWhy(result);
|
|
72317
|
+
errLog(
|
|
72318
|
+
[
|
|
72319
|
+
`CodeRifts claude-hook: BLOCKED${decPart}${eaPart}${idPart}` + (mapped.severity === "UNKNOWN" ? " (unrecognised execution_action)" : ""),
|
|
72320
|
+
...why.lines
|
|
72321
|
+
].join("\n")
|
|
72322
|
+
);
|
|
72323
|
+
return emitTerminal({
|
|
72324
|
+
exitCode: 2,
|
|
72325
|
+
reason: mapped.severity === "UNKNOWN" ? "unknown_action" : "block",
|
|
72326
|
+
severity: mapped.severity,
|
|
72327
|
+
decision: mapped.decision,
|
|
72328
|
+
decisionId: mapped.decisionId,
|
|
72329
|
+
reasons: why.reasonCodes
|
|
72330
|
+
});
|
|
72499
72331
|
}
|
|
72500
|
-
|
|
72501
|
-
|
|
72502
|
-
|
|
72503
|
-
|
|
72504
|
-
|
|
72505
|
-
|
|
72506
|
-
|
|
72507
|
-
|
|
72508
|
-
|
|
72509
|
-
|
|
72510
|
-
|
|
72511
|
-
|
|
72512
|
-
|
|
72513
|
-
|
|
72514
|
-
|
|
72332
|
+
if (mapped.severity === "REQUIRE_APPROVAL") {
|
|
72333
|
+
const why = renderDecisionWhy(result);
|
|
72334
|
+
errLog(
|
|
72335
|
+
[
|
|
72336
|
+
`CodeRifts claude-hook: BLOCKED approval_required${decPart}${eaPart}${idPart}`,
|
|
72337
|
+
...why.lines
|
|
72338
|
+
].join("\n")
|
|
72339
|
+
);
|
|
72340
|
+
return emitTerminal({
|
|
72341
|
+
exitCode: 2,
|
|
72342
|
+
reason: "approval_required",
|
|
72343
|
+
severity: mapped.severity,
|
|
72344
|
+
decision: mapped.decision,
|
|
72345
|
+
decisionId: mapped.decisionId,
|
|
72346
|
+
reasons: why.reasonCodes
|
|
72347
|
+
});
|
|
72348
|
+
}
|
|
72349
|
+
if (mapped.severity === "MONITOR" || mapped.severity === "WARN") {
|
|
72350
|
+
const sinkWired = isMonitoringSinkWired({ env, deps, result });
|
|
72351
|
+
if (!sinkWired) {
|
|
72352
|
+
errLog(
|
|
72353
|
+
`CodeRifts claude-hook: BLOCKED monitoring_unwired${decPart}${eaPart}${idPart} \u2014 CONTINUE_WITH_MONITORING requires CODERIFTS_MONITORING_SINK_WIRED=1 (host assertion; not delivery proof)`
|
|
72354
|
+
);
|
|
72355
|
+
return emitTerminal({
|
|
72356
|
+
exitCode: 2,
|
|
72357
|
+
reason: "monitoring_unwired",
|
|
72358
|
+
severity: mapped.severity === "WARN" ? "MONITOR" : mapped.severity,
|
|
72359
|
+
decision: mapped.decision,
|
|
72360
|
+
decisionId: mapped.decisionId
|
|
72361
|
+
});
|
|
72515
72362
|
}
|
|
72363
|
+
errLog(renderAllowProofBlock({
|
|
72364
|
+
decision: mapped.decision,
|
|
72365
|
+
executionAction: mapped.executionAction,
|
|
72366
|
+
decisionId: mapped.decisionId,
|
|
72367
|
+
fingerprint
|
|
72368
|
+
}));
|
|
72369
|
+
return emitTerminal({
|
|
72370
|
+
exitCode: 0,
|
|
72371
|
+
reason: "allow",
|
|
72372
|
+
severity: mapped.severity === "WARN" ? "MONITOR" : mapped.severity,
|
|
72373
|
+
decision: mapped.decision,
|
|
72374
|
+
decisionId: mapped.decisionId,
|
|
72375
|
+
fingerprint
|
|
72376
|
+
});
|
|
72516
72377
|
}
|
|
72517
|
-
|
|
72378
|
+
errLog(renderAllowProofBlock({
|
|
72379
|
+
decision: mapped.decision,
|
|
72380
|
+
executionAction: mapped.executionAction,
|
|
72381
|
+
decisionId: mapped.decisionId,
|
|
72382
|
+
fingerprint
|
|
72383
|
+
}));
|
|
72384
|
+
return emitTerminal({
|
|
72385
|
+
exitCode: 0,
|
|
72386
|
+
reason: "allow",
|
|
72387
|
+
severity: "ALLOW",
|
|
72388
|
+
decision: mapped.decision,
|
|
72389
|
+
decisionId: mapped.decisionId,
|
|
72390
|
+
fingerprint
|
|
72391
|
+
});
|
|
72518
72392
|
}
|
|
72519
|
-
|
|
72520
|
-
|
|
72521
|
-
|
|
72522
|
-
|
|
72523
|
-
|
|
72393
|
+
module2.exports = {
|
|
72394
|
+
runClaudeHook,
|
|
72395
|
+
parseStdinJson,
|
|
72396
|
+
isSpecPath,
|
|
72397
|
+
deriveAfterContent,
|
|
72398
|
+
mapDecisionSeverity,
|
|
72399
|
+
renderDecisionWhy,
|
|
72400
|
+
resolveApiKey,
|
|
72401
|
+
resolveSpecPath,
|
|
72402
|
+
readGitConfig,
|
|
72403
|
+
isEnvFlag,
|
|
72404
|
+
isStrictMode,
|
|
72405
|
+
isAdvisoryMode,
|
|
72406
|
+
isMonitoringSinkWired,
|
|
72407
|
+
failClosedOrAdvisory,
|
|
72408
|
+
softOrStrictBlock,
|
|
72409
|
+
DEFAULT_SPEC_PATH,
|
|
72410
|
+
CLOSED_ACTIONS,
|
|
72411
|
+
USAGE,
|
|
72412
|
+
appendGuardEventLog,
|
|
72413
|
+
extractFingerprint,
|
|
72414
|
+
fingerprintPrefix,
|
|
72415
|
+
renderAllowProofBlock,
|
|
72416
|
+
HOOK_TRIGGER_SOURCE
|
|
72417
|
+
};
|
|
72418
|
+
}
|
|
72419
|
+
});
|
|
72420
|
+
|
|
72421
|
+
// src/commands/deploy-gate.js
|
|
72422
|
+
var require_deploy_gate2 = __commonJS({
|
|
72423
|
+
"src/commands/deploy-gate.js"(exports2, module2) {
|
|
72424
|
+
"use strict";
|
|
72425
|
+
var fs = require("fs");
|
|
72426
|
+
var path = require("path");
|
|
72427
|
+
var chalk = require_source();
|
|
72428
|
+
var { deployGate } = require_cjs3();
|
|
72429
|
+
var { renderJson } = require_json2();
|
|
72430
|
+
var { renderDecisionWhy, isEnvFlag } = require_claude_hook();
|
|
72431
|
+
if (process.env.NO_COLOR) chalk.level = 0;
|
|
72432
|
+
var REPAIRABLE = /* @__PURE__ */ new Set(["env_mismatch", "stale_artifact", "operation_mismatch", "receipt_not_authorized", "fingerprint_mismatch", "body_hash_mismatch"]);
|
|
72433
|
+
function enforceSignal(options) {
|
|
72434
|
+
return options && options.enforce === true || String(process.env.CODERIFTS_DEPLOY_ENFORCE || "").toLowerCase() === "true";
|
|
72435
|
+
}
|
|
72436
|
+
function isDeployAdvisory(env) {
|
|
72437
|
+
const e = env || process.env;
|
|
72438
|
+
return isEnvFlag(e, "CODERIFTS_DEPLOY_ADVISORY") || isEnvFlag(e, "CODERIFTS_ADVISORY");
|
|
72439
|
+
}
|
|
72440
|
+
function observeCDEnforcement(options = {}) {
|
|
72441
|
+
const envVal = String(process.env.CODERIFTS_DEPLOY_ENFORCE || "").toLowerCase();
|
|
72442
|
+
let enforcement;
|
|
72443
|
+
if (enforceSignal(options)) enforcement = "ENFORCING";
|
|
72444
|
+
else if (envVal === "unknown") enforcement = "UNKNOWN";
|
|
72445
|
+
else enforcement = "ADVISORY";
|
|
72446
|
+
const bypass_possible = String(process.env.CODERIFTS_DEPLOY_NO_BYPASS || "").toLowerCase() !== "true";
|
|
72447
|
+
return {
|
|
72448
|
+
enforcement,
|
|
72449
|
+
bypass_possible,
|
|
72450
|
+
step_is_required: enforcement === "ENFORCING",
|
|
72451
|
+
required_step_name: "CodeRifts / deploy-gate",
|
|
72452
|
+
attestation_source: "cli_flag"
|
|
72453
|
+
};
|
|
72454
|
+
}
|
|
72455
|
+
function deployReportResiduals(state, enforcement_inescapable, enforcement, change_set_rebound) {
|
|
72456
|
+
const out = [];
|
|
72457
|
+
if (state !== "success") return out;
|
|
72458
|
+
if (enforcement_inescapable !== true) {
|
|
72459
|
+
if (enforcement === "ENFORCING") out.push("bypass_open");
|
|
72460
|
+
else if (enforcement === "ADVISORY") out.push("deploy_gate_advisory");
|
|
72461
|
+
else if (enforcement === "ABSENT") out.push("deploy_path_ungated");
|
|
72524
72462
|
}
|
|
72525
|
-
|
|
72526
|
-
|
|
72527
|
-
refs.add(value);
|
|
72528
|
-
} else {
|
|
72529
|
-
extractRefs(value, refs);
|
|
72530
|
-
}
|
|
72463
|
+
if (change_set_rebound !== true) {
|
|
72464
|
+
out.push("change_set_not_rebound");
|
|
72531
72465
|
}
|
|
72532
|
-
return
|
|
72466
|
+
return out;
|
|
72533
72467
|
}
|
|
72534
|
-
function
|
|
72535
|
-
|
|
72536
|
-
const parts = ref.replace("#/", "").split("/");
|
|
72537
|
-
let current = parsed;
|
|
72538
|
-
for (const part of parts) {
|
|
72539
|
-
if (!current || typeof current !== "object") return false;
|
|
72540
|
-
current = current[part];
|
|
72541
|
-
}
|
|
72542
|
-
return current !== void 0;
|
|
72468
|
+
function deployCoverageInput(enforcement_state, inescapable_deploy) {
|
|
72469
|
+
return { enforcement_state, inescapable_deploy: inescapable_deploy === true, applicability_attested: true };
|
|
72543
72470
|
}
|
|
72544
|
-
function
|
|
72545
|
-
|
|
72546
|
-
|
|
72471
|
+
function deployBind({ environment, artifact_id, receipt, observed_cd_enforcement, expected_fingerprint, expected_body_hash }) {
|
|
72472
|
+
const attested_enforcement = observed_cd_enforcement && observed_cd_enforcement.enforcement || "UNKNOWN";
|
|
72473
|
+
if (!receipt) {
|
|
72474
|
+
return {
|
|
72475
|
+
deploy_check_status: "pending",
|
|
72476
|
+
reason: "no_receipt",
|
|
72477
|
+
must_re_preflight: true,
|
|
72478
|
+
attested_enforcement,
|
|
72479
|
+
gate: null,
|
|
72480
|
+
report_residuals: [],
|
|
72481
|
+
coverage_deploy_input: deployCoverageInput(attested_enforcement, false)
|
|
72482
|
+
};
|
|
72547
72483
|
}
|
|
72548
|
-
const
|
|
72549
|
-
|
|
72550
|
-
|
|
72551
|
-
|
|
72552
|
-
|
|
72553
|
-
|
|
72554
|
-
severity: "error",
|
|
72555
|
-
type: "parse_error",
|
|
72556
|
-
message: `Could not parse spec '${name}' as valid YAML or JSON`,
|
|
72557
|
-
specs: [name]
|
|
72558
|
-
});
|
|
72559
|
-
continue;
|
|
72484
|
+
const requiredContext = {
|
|
72485
|
+
operation: "deploy",
|
|
72486
|
+
enforcement: {
|
|
72487
|
+
enforcement: attested_enforcement,
|
|
72488
|
+
// fail-closed: bypass is possible unless observation proved it disabled.
|
|
72489
|
+
bypass_possible: !(observed_cd_enforcement && observed_cd_enforcement.bypass_possible === false)
|
|
72560
72490
|
}
|
|
72561
|
-
|
|
72562
|
-
|
|
72563
|
-
|
|
72564
|
-
|
|
72565
|
-
|
|
72566
|
-
|
|
72567
|
-
specs: [name]
|
|
72568
|
-
});
|
|
72569
|
-
continue;
|
|
72491
|
+
};
|
|
72492
|
+
let change_set_rebound = false;
|
|
72493
|
+
if (attested_enforcement === "ENFORCING") {
|
|
72494
|
+
if (expected_fingerprint != null) {
|
|
72495
|
+
requiredContext.expected_fingerprint = expected_fingerprint;
|
|
72496
|
+
change_set_rebound = true;
|
|
72570
72497
|
}
|
|
72571
|
-
|
|
72498
|
+
if (expected_body_hash != null) requiredContext.expected_body_hash = expected_body_hash;
|
|
72572
72499
|
}
|
|
72573
|
-
const
|
|
72574
|
-
|
|
72575
|
-
|
|
72576
|
-
|
|
72577
|
-
|
|
72578
|
-
|
|
72579
|
-
|
|
72580
|
-
|
|
72500
|
+
const gate = deployGate({ deployTarget: { environment, artifact_id }, receipt, requiredContext });
|
|
72501
|
+
const enforcement_inescapable = attested_enforcement === "ENFORCING" && !!(observed_cd_enforcement && observed_cd_enforcement.bypass_possible === false);
|
|
72502
|
+
const inescapable_deploy = gate.inescapable_deploy === true && change_set_rebound === true;
|
|
72503
|
+
return {
|
|
72504
|
+
deploy_check_status: gate.state,
|
|
72505
|
+
reason: gate.reason,
|
|
72506
|
+
must_re_preflight: REPAIRABLE.has(gate.reason),
|
|
72507
|
+
attested_enforcement,
|
|
72508
|
+
gate: { deploy_allowed: gate.deploy_allowed, reason: gate.reason, inescapable_deploy },
|
|
72509
|
+
report_residuals: deployReportResiduals(gate.state, enforcement_inescapable, attested_enforcement, change_set_rebound),
|
|
72510
|
+
coverage_deploy_input: deployCoverageInput(attested_enforcement, inescapable_deploy)
|
|
72511
|
+
};
|
|
72512
|
+
}
|
|
72513
|
+
function clampExit(deployCheckStatus, advisory) {
|
|
72514
|
+
if (deployCheckStatus === "success") return 0;
|
|
72515
|
+
if (advisory === true) return 0;
|
|
72516
|
+
return 1;
|
|
72517
|
+
}
|
|
72518
|
+
var ADVISORY_LABEL = "ADVISORY MODE \u2014 this gate did not block";
|
|
72519
|
+
var SOFTEN_HINT = "set CODERIFTS_DEPLOY_ADVISORY=1 to soften";
|
|
72520
|
+
function renderPolicyStderr(bind, receipt, advisory) {
|
|
72521
|
+
const lines = [
|
|
72522
|
+
`CodeRifts deploy-gate: BLOCKED reason=${bind.reason} status=${bind.deploy_check_status}`
|
|
72523
|
+
];
|
|
72524
|
+
if (receipt && typeof receipt === "object") {
|
|
72525
|
+
const why = renderDecisionWhy(receipt);
|
|
72526
|
+
for (const l of why.lines) lines.push(l);
|
|
72581
72527
|
}
|
|
72582
|
-
|
|
72583
|
-
|
|
72584
|
-
const specNames = owners.map((o) => o.spec);
|
|
72585
|
-
issues.push({
|
|
72586
|
-
severity: "warning",
|
|
72587
|
-
type: "endpoint_collision",
|
|
72588
|
-
message: `Endpoint '${endpoint}' is defined in multiple specs: ${specNames.join(", ")}`,
|
|
72589
|
-
specs: specNames
|
|
72590
|
-
});
|
|
72591
|
-
}
|
|
72528
|
+
if (bind.must_re_preflight) {
|
|
72529
|
+
lines.push("- action: re-preflight this { environment, artifact } \u2014 the receipt does not authorize it.");
|
|
72592
72530
|
}
|
|
72593
|
-
|
|
72594
|
-
|
|
72595
|
-
|
|
72596
|
-
|
|
72597
|
-
|
|
72598
|
-
|
|
72531
|
+
lines.push(advisory ? ADVISORY_LABEL : SOFTEN_HINT);
|
|
72532
|
+
return lines.join("\n");
|
|
72533
|
+
}
|
|
72534
|
+
function renderInfraStderr({ site, why, retryable = true, advisory = false }) {
|
|
72535
|
+
const lines = [
|
|
72536
|
+
`CodeRifts deploy-gate: INFRA ${site} \u2014 ${why}`,
|
|
72537
|
+
`- retryable: ${retryable ? "true" : "false"}`,
|
|
72538
|
+
"- this is not a policy BLOCK"
|
|
72539
|
+
];
|
|
72540
|
+
lines.push(advisory ? ADVISORY_LABEL : SOFTEN_HINT);
|
|
72541
|
+
return lines.join("\n");
|
|
72542
|
+
}
|
|
72543
|
+
function inspectReceiptFile(filePath) {
|
|
72544
|
+
if (!filePath) return { kind: "absent", receipt: null };
|
|
72545
|
+
const resolved = path.resolve(filePath);
|
|
72546
|
+
if (!fs.existsSync(resolved)) return { kind: "missing_file", receipt: null };
|
|
72547
|
+
try {
|
|
72548
|
+
const parsed = JSON.parse(fs.readFileSync(resolved, "utf-8"));
|
|
72549
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
72550
|
+
return { kind: "malformed", receipt: null };
|
|
72599
72551
|
}
|
|
72552
|
+
return { kind: "ok", receipt: parsed };
|
|
72553
|
+
} catch (_) {
|
|
72554
|
+
return { kind: "malformed", receipt: null };
|
|
72600
72555
|
}
|
|
72601
|
-
|
|
72602
|
-
|
|
72603
|
-
|
|
72604
|
-
|
|
72605
|
-
|
|
72606
|
-
|
|
72607
|
-
|
|
72608
|
-
|
|
72609
|
-
message: `Schema '${schemaName}' has conflicting definitions across specs: ${specNames.join(", ")}`,
|
|
72610
|
-
specs: specNames
|
|
72611
|
-
});
|
|
72612
|
-
}
|
|
72613
|
-
}
|
|
72556
|
+
}
|
|
72557
|
+
function readReceiptFile(filePath) {
|
|
72558
|
+
return inspectReceiptFile(filePath).receipt;
|
|
72559
|
+
}
|
|
72560
|
+
function extractDecisionIdFromReceipt(receipt) {
|
|
72561
|
+
if (!receipt || typeof receipt !== "object") return null;
|
|
72562
|
+
if (typeof receipt.decision_id === "string" && receipt.decision_id.trim()) {
|
|
72563
|
+
return receipt.decision_id.trim();
|
|
72614
72564
|
}
|
|
72615
|
-
|
|
72616
|
-
|
|
72617
|
-
|
|
72618
|
-
for (const scope of used) {
|
|
72619
|
-
if (!defined.has(scope)) {
|
|
72620
|
-
issues.push({
|
|
72621
|
-
severity: "warning",
|
|
72622
|
-
type: "undefined_scope",
|
|
72623
|
-
message: `Scope '${scope}' is used in '${name}' but not defined in securitySchemes`,
|
|
72624
|
-
specs: [name]
|
|
72625
|
-
});
|
|
72626
|
-
}
|
|
72627
|
-
}
|
|
72628
|
-
for (const scope of defined) {
|
|
72629
|
-
if (!used.has(scope)) {
|
|
72630
|
-
issues.push({
|
|
72631
|
-
severity: "info",
|
|
72632
|
-
type: "unused_scope",
|
|
72633
|
-
message: `Scope '${scope}' is defined in '${name}' but never used in any operation`,
|
|
72634
|
-
specs: [name]
|
|
72635
|
-
});
|
|
72636
|
-
}
|
|
72637
|
-
}
|
|
72565
|
+
const nested = receipt.decision_result;
|
|
72566
|
+
if (nested && typeof nested === "object" && typeof nested.decision_id === "string" && nested.decision_id.trim()) {
|
|
72567
|
+
return nested.decision_id.trim();
|
|
72638
72568
|
}
|
|
72639
|
-
|
|
72640
|
-
|
|
72641
|
-
|
|
72642
|
-
|
|
72643
|
-
|
|
72644
|
-
|
|
72645
|
-
|
|
72646
|
-
|
|
72647
|
-
|
|
72648
|
-
|
|
72649
|
-
|
|
72650
|
-
|
|
72569
|
+
return null;
|
|
72570
|
+
}
|
|
72571
|
+
function formatOutcomeReportHint(decisionId) {
|
|
72572
|
+
const id = String(decisionId);
|
|
72573
|
+
return [
|
|
72574
|
+
"# report the deploy outcome after your deploy step:",
|
|
72575
|
+
`coderifts outcome <deploy_succeeded|deploy_failed|rolled_back> --decision ${id}`
|
|
72576
|
+
].join("\n");
|
|
72577
|
+
}
|
|
72578
|
+
function renderDeployGateTerminal(bind, enforce) {
|
|
72579
|
+
const g = bind.gate;
|
|
72580
|
+
const color = bind.deploy_check_status === "success" ? chalk.green : bind.deploy_check_status === "failure" ? chalk.red : chalk.yellow;
|
|
72581
|
+
const lines = [];
|
|
72582
|
+
lines.push("");
|
|
72583
|
+
lines.push(chalk.bold(` CodeRifts deploy-gate \u2014 ${color(bind.deploy_check_status.toUpperCase())}`));
|
|
72584
|
+
lines.push(` Reason: ${bind.reason}`);
|
|
72585
|
+
lines.push(` Enforcement: ${bind.attested_enforcement}`);
|
|
72586
|
+
lines.push(` inescapable_deploy: ${g ? g.inescapable_deploy : false}`);
|
|
72587
|
+
if (bind.report_residuals.length) lines.push(` Residuals: ${bind.report_residuals.join(", ")}`);
|
|
72588
|
+
if (bind.must_re_preflight) lines.push(chalk.yellow(" Action: re-preflight this { environment, artifact } \u2014 the receipt does not authorize it."));
|
|
72589
|
+
lines.push("");
|
|
72590
|
+
lines.push(enforce ? chalk.dim(" Attested ENFORCING (inescapable_deploy claim). Exit is fail-closed by default.") : chalk.dim(" Fail-closed default \u2014 a failing gate exits non-zero. " + SOFTEN_HINT + "."));
|
|
72591
|
+
lines.push("");
|
|
72592
|
+
return lines.join("\n");
|
|
72593
|
+
}
|
|
72594
|
+
async function runDeployGate(options = {}) {
|
|
72595
|
+
const env = options.envObj || process.env;
|
|
72596
|
+
const advisory = isDeployAdvisory(env);
|
|
72597
|
+
const errLog = options.errLog || ((m) => console.error(String(m)));
|
|
72598
|
+
const outLog = options.outLog || ((m) => console.log(String(m)));
|
|
72599
|
+
const exitFn = options.exitFn || ((code2) => {
|
|
72600
|
+
process.exit(code2);
|
|
72601
|
+
});
|
|
72602
|
+
const environment = options.env;
|
|
72603
|
+
const artifactId = options.artifact;
|
|
72604
|
+
if (!environment || !artifactId) {
|
|
72605
|
+
errLog(renderInfraStderr({
|
|
72606
|
+
site: "missing_inputs",
|
|
72607
|
+
why: "--env and --artifact are required",
|
|
72608
|
+
retryable: true,
|
|
72609
|
+
advisory
|
|
72610
|
+
}));
|
|
72611
|
+
exitFn(advisory ? 0 : 1);
|
|
72612
|
+
return { exitCode: advisory ? 0 : 1, failure_class: "infra" };
|
|
72651
72613
|
}
|
|
72652
|
-
|
|
72653
|
-
|
|
72654
|
-
|
|
72655
|
-
|
|
72656
|
-
|
|
72614
|
+
const enforce = enforceSignal(options);
|
|
72615
|
+
const inspected = inspectReceiptFile(options.receipt);
|
|
72616
|
+
if (inspected.kind === "malformed") {
|
|
72617
|
+
errLog(renderInfraStderr({
|
|
72618
|
+
site: "malformed_receipt",
|
|
72619
|
+
why: `receipt file is not valid JSON: ${options.receipt}`,
|
|
72620
|
+
retryable: true,
|
|
72621
|
+
advisory
|
|
72622
|
+
}));
|
|
72623
|
+
exitFn(advisory ? 0 : 1);
|
|
72624
|
+
return { exitCode: advisory ? 0 : 1, failure_class: "infra" };
|
|
72657
72625
|
}
|
|
72658
|
-
const
|
|
72659
|
-
|
|
72660
|
-
|
|
72661
|
-
|
|
72662
|
-
|
|
72663
|
-
|
|
72664
|
-
|
|
72665
|
-
|
|
72626
|
+
const receipt = inspected.receipt;
|
|
72627
|
+
const observed = observeCDEnforcement({ enforce });
|
|
72628
|
+
const bind = deployBind({ environment, artifact_id: artifactId, receipt, observed_cd_enforcement: observed });
|
|
72629
|
+
const code = clampExit(bind.deploy_check_status, advisory);
|
|
72630
|
+
const decisionId = extractDecisionIdFromReceipt(receipt);
|
|
72631
|
+
const failureClass = bind.deploy_check_status === "success" ? null : "policy";
|
|
72632
|
+
if (bind.deploy_check_status !== "success") {
|
|
72633
|
+
errLog(renderPolicyStderr(bind, receipt, advisory));
|
|
72634
|
+
} else if (advisory) {
|
|
72635
|
+
errLog(ADVISORY_LABEL);
|
|
72636
|
+
}
|
|
72637
|
+
if (options.json) {
|
|
72638
|
+
outLog(renderJson({
|
|
72639
|
+
command: "deploy-gate",
|
|
72640
|
+
environment,
|
|
72641
|
+
artifact_id: artifactId,
|
|
72642
|
+
phase: advisory ? "advisory" : "fail_closed",
|
|
72643
|
+
exit_code: code,
|
|
72644
|
+
decision_id: decisionId,
|
|
72645
|
+
failure_class: failureClass,
|
|
72646
|
+
retryable: false,
|
|
72647
|
+
...bind
|
|
72648
|
+
}));
|
|
72649
|
+
} else {
|
|
72650
|
+
outLog(renderDeployGateTerminal(bind, enforce));
|
|
72651
|
+
if (decisionId) {
|
|
72652
|
+
outLog(formatOutcomeReportHint(decisionId));
|
|
72653
|
+
outLog("");
|
|
72666
72654
|
}
|
|
72667
|
-
}
|
|
72655
|
+
}
|
|
72656
|
+
exitFn(code);
|
|
72657
|
+
return { exitCode: code, failure_class: failureClass };
|
|
72668
72658
|
}
|
|
72669
72659
|
module2.exports = {
|
|
72670
|
-
|
|
72671
|
-
|
|
72672
|
-
|
|
72673
|
-
|
|
72674
|
-
|
|
72675
|
-
|
|
72676
|
-
|
|
72677
|
-
|
|
72678
|
-
|
|
72679
|
-
|
|
72660
|
+
runDeployGate,
|
|
72661
|
+
deployBind,
|
|
72662
|
+
observeCDEnforcement,
|
|
72663
|
+
clampExit,
|
|
72664
|
+
readReceiptFile,
|
|
72665
|
+
inspectReceiptFile,
|
|
72666
|
+
extractDecisionIdFromReceipt,
|
|
72667
|
+
formatOutcomeReportHint,
|
|
72668
|
+
renderDeployGateTerminal,
|
|
72669
|
+
renderPolicyStderr,
|
|
72670
|
+
renderInfraStderr,
|
|
72671
|
+
isDeployAdvisory,
|
|
72672
|
+
ADVISORY_LABEL,
|
|
72673
|
+
SOFTEN_HINT
|
|
72680
72674
|
};
|
|
72681
72675
|
}
|
|
72682
72676
|
});
|
|
72683
72677
|
|
|
72684
|
-
// src/commands/
|
|
72685
|
-
var
|
|
72686
|
-
"src/commands/
|
|
72678
|
+
// src/commands/publish-gate.js
|
|
72679
|
+
var require_publish_gate = __commonJS({
|
|
72680
|
+
"src/commands/publish-gate.js"(exports2, module2) {
|
|
72687
72681
|
"use strict";
|
|
72688
72682
|
var fs = require("fs");
|
|
72689
72683
|
var path = require("path");
|
|
72684
|
+
var { execFileSync } = require("child_process");
|
|
72690
72685
|
var chalk = require_source();
|
|
72691
|
-
var {
|
|
72692
|
-
var {
|
|
72693
|
-
validateRegistry,
|
|
72694
|
-
safeParse,
|
|
72695
|
-
validateOpenApiSpec
|
|
72696
|
-
} = require_registry_validation_core();
|
|
72686
|
+
var { getApiKey } = require_config();
|
|
72687
|
+
var { cloudDiff } = require_cloud();
|
|
72697
72688
|
if (process.env.NO_COLOR) chalk.level = 0;
|
|
72698
|
-
var
|
|
72699
|
-
|
|
72700
|
-
|
|
72701
|
-
|
|
72702
|
-
|
|
72703
|
-
|
|
72704
|
-
|
|
72705
|
-
|
|
72706
|
-
|
|
72707
|
-
|
|
72708
|
-
|
|
72709
|
-
|
|
72710
|
-
|
|
72711
|
-
|
|
72712
|
-
|
|
72713
|
-
}
|
|
72714
|
-
for (const ent of entries) {
|
|
72715
|
-
const name = ent.name;
|
|
72716
|
-
if (name === "node_modules" || name.startsWith(".")) continue;
|
|
72717
|
-
const abs = path.join(absDir, name);
|
|
72718
|
-
let isDir = ent.isDirectory && ent.isDirectory();
|
|
72719
|
-
let isFile = ent.isFile && ent.isFile();
|
|
72720
|
-
if (!isDir && !isFile) {
|
|
72721
|
-
try {
|
|
72722
|
-
const st = statSync(abs);
|
|
72723
|
-
isDir = st.isDirectory();
|
|
72724
|
-
isFile = st.isFile();
|
|
72725
|
-
} catch (err) {
|
|
72726
|
-
const e = new Error(`Cannot stat ${abs}: ${err && err.message}`);
|
|
72727
|
-
e.code = "GATE_ERROR";
|
|
72728
|
-
e.path = abs;
|
|
72729
|
-
throw e;
|
|
72730
|
-
}
|
|
72731
|
-
}
|
|
72732
|
-
if (isDir) {
|
|
72733
|
-
walk(abs);
|
|
72734
|
-
continue;
|
|
72735
|
-
}
|
|
72736
|
-
if (isFile) {
|
|
72737
|
-
const ext = path.extname(name).toLowerCase();
|
|
72738
|
-
if (SPEC_EXT.has(ext)) out.push(abs);
|
|
72739
|
-
}
|
|
72740
|
-
}
|
|
72741
|
-
}
|
|
72742
|
-
walk(rootDir);
|
|
72743
|
-
return out.sort();
|
|
72744
|
-
}
|
|
72745
|
-
function relPosix(rootDir, absPath) {
|
|
72746
|
-
let rel = path.relative(rootDir, absPath);
|
|
72747
|
-
if (path.sep !== "/") rel = rel.split(path.sep).join("/");
|
|
72748
|
-
return rel;
|
|
72689
|
+
var ZERO_SHA = "0000000000000000000000000000000000000000";
|
|
72690
|
+
var CLOSED_ACTIONS = /* @__PURE__ */ new Set([
|
|
72691
|
+
"CONTINUE",
|
|
72692
|
+
"CONTINUE_WITH_MONITORING",
|
|
72693
|
+
"REQUEST_APPROVAL",
|
|
72694
|
+
"STOP"
|
|
72695
|
+
]);
|
|
72696
|
+
var PERMIT_ACTIONS = /* @__PURE__ */ new Set(["CONTINUE", "CONTINUE_WITH_MONITORING"]);
|
|
72697
|
+
function defaultGit(args, cwd) {
|
|
72698
|
+
return execFileSync("git", args, {
|
|
72699
|
+
cwd: cwd || process.cwd(),
|
|
72700
|
+
encoding: "utf8",
|
|
72701
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
72702
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
72703
|
+
}).trim();
|
|
72749
72704
|
}
|
|
72750
|
-
function
|
|
72705
|
+
function gitShow(ref, filePath, { gitImpl = defaultGit, cwd } = {}) {
|
|
72751
72706
|
try {
|
|
72752
|
-
const
|
|
72753
|
-
return { ok: true, content:
|
|
72707
|
+
const out = gitImpl(["show", `${ref}:${filePath}`], cwd);
|
|
72708
|
+
return { ok: true, content: out == null ? "" : String(out) };
|
|
72754
72709
|
} catch (err) {
|
|
72755
|
-
const msg = err && err.
|
|
72710
|
+
const msg = err && err.stderr ? String(err.stderr) : err && err.message || String(err);
|
|
72756
72711
|
return {
|
|
72757
72712
|
ok: false,
|
|
72758
|
-
code: "
|
|
72759
|
-
message: `
|
|
72760
|
-
path: absPath
|
|
72713
|
+
code: "GIT_ERROR",
|
|
72714
|
+
message: `git error reading ${ref}:${filePath}: ${msg.slice(0, 300)}`
|
|
72761
72715
|
};
|
|
72762
72716
|
}
|
|
72763
72717
|
}
|
|
72764
|
-
function
|
|
72765
|
-
|
|
72766
|
-
readdirSync = fs.readdirSync.bind(fs),
|
|
72767
|
-
readFileSync = fs.readFileSync.bind(fs),
|
|
72768
|
-
statSync = fs.statSync.bind(fs)
|
|
72769
|
-
} = {}) {
|
|
72770
|
-
const rootDir = path.resolve(dir);
|
|
72771
|
-
let candidates;
|
|
72718
|
+
function readPackageVersion(cwd, readFile = fs.readFileSync) {
|
|
72719
|
+
const pkgPath = path.join(cwd || process.cwd(), "package.json");
|
|
72772
72720
|
try {
|
|
72773
|
-
|
|
72774
|
-
|
|
72775
|
-
return
|
|
72776
|
-
|
|
72777
|
-
|
|
72778
|
-
message: err.message || String(err),
|
|
72779
|
-
path: err.path
|
|
72780
|
-
};
|
|
72721
|
+
const raw = readFile(pkgPath, "utf8");
|
|
72722
|
+
const pkg2 = JSON.parse(raw);
|
|
72723
|
+
return pkg2 && typeof pkg2.version === "string" ? pkg2.version : null;
|
|
72724
|
+
} catch {
|
|
72725
|
+
return null;
|
|
72781
72726
|
}
|
|
72782
|
-
|
|
72783
|
-
|
|
72727
|
+
}
|
|
72728
|
+
function resolveBeforeSpec(specPath, {
|
|
72729
|
+
gitImpl = defaultGit,
|
|
72730
|
+
cwd = process.cwd(),
|
|
72731
|
+
readFile = fs.readFileSync,
|
|
72732
|
+
packageVersion = null
|
|
72733
|
+
} = {}) {
|
|
72734
|
+
const version = packageVersion != null ? packageVersion : readPackageVersion(cwd, readFile);
|
|
72735
|
+
const tried = [];
|
|
72736
|
+
if (version) {
|
|
72737
|
+
const tags = [`v${version}`, version];
|
|
72738
|
+
for (const tag of tags) {
|
|
72739
|
+
tried.push(`tag:${tag}`);
|
|
72740
|
+
try {
|
|
72741
|
+
gitImpl(["rev-parse", "--verify", `${tag}^{commit}`], cwd);
|
|
72742
|
+
} catch {
|
|
72743
|
+
continue;
|
|
72744
|
+
}
|
|
72745
|
+
const shown = gitShow(tag, specPath, { gitImpl, cwd });
|
|
72746
|
+
if (!shown.ok) {
|
|
72747
|
+
return {
|
|
72748
|
+
ok: false,
|
|
72749
|
+
code: shown.code || "GIT_ERROR",
|
|
72750
|
+
message: shown.message,
|
|
72751
|
+
tried
|
|
72752
|
+
};
|
|
72753
|
+
}
|
|
72754
|
+
if (shown.content.trim() === "") {
|
|
72755
|
+
return {
|
|
72756
|
+
ok: false,
|
|
72757
|
+
code: "EMPTY_BEFORE",
|
|
72758
|
+
message: `Empty contract artifact at tag ${tag}:${specPath}. Refusing to treat empty before as NEW_ARTIFACT (would false-pass enforce).`,
|
|
72759
|
+
tried
|
|
72760
|
+
};
|
|
72761
|
+
}
|
|
72762
|
+
return { ok: true, content: shown.content, source: `tag:${tag}`, tried };
|
|
72763
|
+
}
|
|
72764
|
+
} else {
|
|
72765
|
+
tried.push("package.json:version (missing)");
|
|
72784
72766
|
}
|
|
72785
|
-
const
|
|
72786
|
-
|
|
72787
|
-
|
|
72788
|
-
|
|
72789
|
-
|
|
72767
|
+
const bases = ["origin/main", "origin/master", "main", "master"];
|
|
72768
|
+
for (const base of bases) {
|
|
72769
|
+
tried.push(`merge-base:${base}`);
|
|
72770
|
+
let mb;
|
|
72771
|
+
try {
|
|
72772
|
+
mb = gitImpl(["merge-base", "HEAD", base], cwd);
|
|
72773
|
+
} catch {
|
|
72774
|
+
continue;
|
|
72775
|
+
}
|
|
72776
|
+
if (!mb || mb === ZERO_SHA) continue;
|
|
72777
|
+
const shown = gitShow(mb, specPath, { gitImpl, cwd });
|
|
72778
|
+
if (!shown.ok) {
|
|
72790
72779
|
return {
|
|
72791
72780
|
ok: false,
|
|
72792
|
-
code: "
|
|
72793
|
-
message:
|
|
72794
|
-
|
|
72781
|
+
code: shown.code || "GIT_ERROR",
|
|
72782
|
+
message: shown.message,
|
|
72783
|
+
tried
|
|
72795
72784
|
};
|
|
72796
72785
|
}
|
|
72797
|
-
|
|
72798
|
-
|
|
72799
|
-
|
|
72800
|
-
|
|
72801
|
-
|
|
72802
|
-
|
|
72803
|
-
|
|
72804
|
-
if (!shape.valid) {
|
|
72805
|
-
if (shape.error && shape.error.includes("Missing required field: 'openapi' or 'swagger'")) {
|
|
72806
|
-
skippedNonSpec += 1;
|
|
72807
|
-
continue;
|
|
72808
|
-
}
|
|
72809
|
-
specs.push({ name, spec: read.content });
|
|
72810
|
-
continue;
|
|
72786
|
+
if (shown.content.trim() === "") {
|
|
72787
|
+
return {
|
|
72788
|
+
ok: false,
|
|
72789
|
+
code: "EMPTY_BEFORE",
|
|
72790
|
+
message: `Empty contract artifact at merge-base ${mb}:${specPath} (${base}). Refusing to treat empty before as NEW_ARTIFACT (would false-pass enforce).`,
|
|
72791
|
+
tried
|
|
72792
|
+
};
|
|
72811
72793
|
}
|
|
72812
|
-
|
|
72794
|
+
return {
|
|
72795
|
+
ok: true,
|
|
72796
|
+
content: shown.content,
|
|
72797
|
+
source: `merge-base:${base}@${mb.slice(0, 12)}`,
|
|
72798
|
+
tried
|
|
72799
|
+
};
|
|
72813
72800
|
}
|
|
72814
72801
|
return {
|
|
72815
|
-
ok:
|
|
72816
|
-
|
|
72817
|
-
|
|
72818
|
-
|
|
72819
|
-
rootDir
|
|
72802
|
+
ok: false,
|
|
72803
|
+
code: "BEFORE_UNRESOLVED",
|
|
72804
|
+
message: `Could not resolve a non-empty before-spec for ${specPath}. Tried: (a) git tag of package.json version, (b) merge-base with origin/main. Attempts: ${tried.join(", ")}. Fail-closed \u2014 will not publish without a baseline.`,
|
|
72805
|
+
tried
|
|
72820
72806
|
};
|
|
72821
72807
|
}
|
|
72822
|
-
function
|
|
72823
|
-
|
|
72824
|
-
|
|
72825
|
-
|
|
72826
|
-
|
|
72808
|
+
function resolveAfterSpec(specPath, {
|
|
72809
|
+
cwd = process.cwd(),
|
|
72810
|
+
readFile = fs.readFileSync,
|
|
72811
|
+
exists = fs.existsSync
|
|
72812
|
+
} = {}) {
|
|
72813
|
+
const resolved = path.isAbsolute(specPath) ? specPath : path.join(cwd, specPath);
|
|
72814
|
+
try {
|
|
72815
|
+
if (!exists(resolved)) {
|
|
72816
|
+
return {
|
|
72817
|
+
ok: false,
|
|
72818
|
+
code: "AFTER_MISSING",
|
|
72819
|
+
message: `Working-tree contract artifact not found: ${specPath}`
|
|
72820
|
+
};
|
|
72821
|
+
}
|
|
72822
|
+
const content = readFile(resolved, "utf8");
|
|
72823
|
+
if (content == null || String(content).trim() === "") {
|
|
72824
|
+
return {
|
|
72825
|
+
ok: false,
|
|
72826
|
+
code: "AFTER_EMPTY",
|
|
72827
|
+
message: `Working-tree contract artifact is empty: ${specPath}`
|
|
72828
|
+
};
|
|
72829
|
+
}
|
|
72830
|
+
return { ok: true, content: String(content), path: resolved };
|
|
72831
|
+
} catch (err) {
|
|
72832
|
+
return {
|
|
72833
|
+
ok: false,
|
|
72834
|
+
code: "AFTER_READ_ERROR",
|
|
72835
|
+
message: `Failed to read working-tree ${specPath}: ${err && err.message}`
|
|
72836
|
+
};
|
|
72827
72837
|
}
|
|
72828
|
-
return false;
|
|
72829
72838
|
}
|
|
72830
|
-
function
|
|
72831
|
-
|
|
72832
|
-
|
|
72833
|
-
|
|
72834
|
-
|
|
72835
|
-
|
|
72839
|
+
function evaluatePublishPermission(result) {
|
|
72840
|
+
if (!result || typeof result !== "object") {
|
|
72841
|
+
return {
|
|
72842
|
+
allow: false,
|
|
72843
|
+
execution_action: null,
|
|
72844
|
+
decision: null,
|
|
72845
|
+
policy: "fail_closed:unreadable_response"
|
|
72846
|
+
};
|
|
72836
72847
|
}
|
|
72837
|
-
|
|
72848
|
+
const env = result.decision_result && typeof result.decision_result === "object" ? result.decision_result : null;
|
|
72849
|
+
let ea = null;
|
|
72850
|
+
if (env && typeof env.execution_action === "string") ea = env.execution_action;
|
|
72851
|
+
else if (typeof result.execution_action === "string") ea = result.execution_action;
|
|
72852
|
+
const decision = env && env.decision || result.decision || result.omega_decision || null;
|
|
72853
|
+
if (ea && CLOSED_ACTIONS.has(ea)) {
|
|
72854
|
+
const allow = PERMIT_ACTIONS.has(ea);
|
|
72855
|
+
return {
|
|
72856
|
+
allow,
|
|
72857
|
+
execution_action: ea,
|
|
72858
|
+
decision: decision || null,
|
|
72859
|
+
policy: allow ? `permit:execution_action=${ea}` : `block:execution_action=${ea}`
|
|
72860
|
+
};
|
|
72861
|
+
}
|
|
72862
|
+
if (ea != null && ea !== "" && !CLOSED_ACTIONS.has(ea)) {
|
|
72863
|
+
return {
|
|
72864
|
+
allow: false,
|
|
72865
|
+
execution_action: ea,
|
|
72866
|
+
decision: decision || null,
|
|
72867
|
+
policy: `block:unrecognised_execution_action=${ea}`
|
|
72868
|
+
};
|
|
72869
|
+
}
|
|
72870
|
+
if (decision === "BLOCK" || decision === "REQUIRE_APPROVAL") {
|
|
72871
|
+
return {
|
|
72872
|
+
allow: false,
|
|
72873
|
+
execution_action: null,
|
|
72874
|
+
decision,
|
|
72875
|
+
policy: `block:decision=${decision}`
|
|
72876
|
+
};
|
|
72877
|
+
}
|
|
72878
|
+
if (decision === "ALLOW" || decision === "WARN" || decision === "PASS") {
|
|
72879
|
+
return {
|
|
72880
|
+
allow: true,
|
|
72881
|
+
execution_action: null,
|
|
72882
|
+
decision,
|
|
72883
|
+
policy: `permit:decision=${decision}`
|
|
72884
|
+
};
|
|
72885
|
+
}
|
|
72886
|
+
const omega = result.omega_decision;
|
|
72887
|
+
if (omega === "BLOCK" || omega === "REQUIRE_APPROVAL") {
|
|
72888
|
+
return {
|
|
72889
|
+
allow: false,
|
|
72890
|
+
execution_action: null,
|
|
72891
|
+
decision: omega,
|
|
72892
|
+
policy: `block:omega_decision=${omega}`
|
|
72893
|
+
};
|
|
72894
|
+
}
|
|
72895
|
+
return {
|
|
72896
|
+
allow: false,
|
|
72897
|
+
execution_action: ea,
|
|
72898
|
+
decision: decision || omega || null,
|
|
72899
|
+
policy: "fail_closed:no_permission_signal"
|
|
72900
|
+
};
|
|
72838
72901
|
}
|
|
72839
|
-
function
|
|
72840
|
-
if (
|
|
72841
|
-
|
|
72842
|
-
|
|
72902
|
+
function extractReceiptRef(result) {
|
|
72903
|
+
if (!result || typeof result !== "object") return null;
|
|
72904
|
+
const env = result.decision_result;
|
|
72905
|
+
if (env && env.receipt && typeof env.receipt.token === "string") {
|
|
72906
|
+
return env.receipt.token.slice(0, 24) + (env.receipt.token.length > 24 ? "\u2026" : "");
|
|
72907
|
+
}
|
|
72908
|
+
if (env && typeof env.decision_id === "string") return env.decision_id;
|
|
72909
|
+
if (typeof result.decision_id === "string") return result.decision_id;
|
|
72910
|
+
if (typeof result.fingerprint === "string") return result.fingerprint;
|
|
72911
|
+
if (env && typeof env.fingerprint === "string") return env.fingerprint;
|
|
72912
|
+
return null;
|
|
72843
72913
|
}
|
|
72844
|
-
function
|
|
72845
|
-
|
|
72846
|
-
|
|
72847
|
-
|
|
72848
|
-
|
|
72849
|
-
|
|
72914
|
+
async function defaultPreflight(before, after, { apiKey } = {}) {
|
|
72915
|
+
const key = process.env.CODERIFTS_FORCE_LOCAL_PREFLIGHT ? null : apiKey != null ? apiKey : getApiKey();
|
|
72916
|
+
if (key) {
|
|
72917
|
+
return cloudDiff(before, after, key);
|
|
72918
|
+
}
|
|
72919
|
+
const yaml = require_js_yaml();
|
|
72920
|
+
const { diffSpecs } = require_api2();
|
|
72921
|
+
let oldSpec;
|
|
72922
|
+
let newSpec;
|
|
72923
|
+
try {
|
|
72924
|
+
oldSpec = yaml.load(before);
|
|
72925
|
+
newSpec = yaml.load(after);
|
|
72926
|
+
} catch (e) {
|
|
72927
|
+
const err = new Error(`Failed to parse specs: ${e.message}`);
|
|
72928
|
+
err.code = "PREFLIGHT_UNREACHABLE";
|
|
72929
|
+
throw err;
|
|
72930
|
+
}
|
|
72931
|
+
let diffResult;
|
|
72932
|
+
try {
|
|
72933
|
+
diffResult = await diffSpecs({
|
|
72934
|
+
sourceSpec: { content: JSON.stringify(oldSpec), location: "before.json", format: "openapi3" },
|
|
72935
|
+
destinationSpec: { content: JSON.stringify(newSpec), location: "after.json", format: "openapi3" }
|
|
72936
|
+
});
|
|
72937
|
+
} catch (e) {
|
|
72938
|
+
const err = new Error(`Local preflight engine error: ${e.message}`);
|
|
72939
|
+
err.code = "PREFLIGHT_UNREACHABLE";
|
|
72940
|
+
throw err;
|
|
72850
72941
|
}
|
|
72942
|
+
const breaking = (diffResult.breakingDifferences || []).length;
|
|
72943
|
+
const decision = breaking > 0 ? "BLOCK" : "ALLOW";
|
|
72944
|
+
const execution_action = breaking > 0 ? "STOP" : "CONTINUE";
|
|
72945
|
+
return {
|
|
72946
|
+
decision,
|
|
72947
|
+
omega_decision: decision,
|
|
72948
|
+
execution_action,
|
|
72949
|
+
decision_result: {
|
|
72950
|
+
decision,
|
|
72951
|
+
execution_action,
|
|
72952
|
+
decision_id: `local-${Date.now()}`
|
|
72953
|
+
},
|
|
72954
|
+
breaking_changes: diffResult.breakingDifferences || [],
|
|
72955
|
+
risk_score: Math.min(breaking * 15, 100)
|
|
72956
|
+
};
|
|
72851
72957
|
}
|
|
72852
|
-
function
|
|
72958
|
+
async function runPublishGate(options = {}, deps = {}) {
|
|
72959
|
+
const cwd = deps.cwd || process.cwd();
|
|
72960
|
+
const gitImpl = deps.gitImpl || defaultGit;
|
|
72961
|
+
const readFile = deps.readFile || fs.readFileSync.bind(fs);
|
|
72962
|
+
const exists = deps.exists || fs.existsSync.bind(fs);
|
|
72963
|
+
const preflightFn = deps.preflightFn || defaultPreflight;
|
|
72853
72964
|
const log = deps.log || console.log.bind(console);
|
|
72854
72965
|
const logErr = deps.logErr || console.error.bind(console);
|
|
72855
|
-
|
|
72856
|
-
|
|
72857
|
-
|
|
72858
|
-
|
|
72859
|
-
|
|
72860
|
-
|
|
72861
|
-
|
|
72862
|
-
if (!existsSync(rootDir)) {
|
|
72863
|
-
const msg = `CodeRifts registry-gate: GATE_ERROR \u2014 directory not found: ${rootDir}`;
|
|
72864
|
-
logErr(chalk.red(msg));
|
|
72865
|
-
return { ok: false, exitCode: 1, code: "GATE_ERROR", message: msg };
|
|
72966
|
+
let specPath = options.spec;
|
|
72967
|
+
if (!specPath) {
|
|
72968
|
+
try {
|
|
72969
|
+
specPath = gitImpl(["config", "coderifts.specPath"], cwd);
|
|
72970
|
+
} catch {
|
|
72971
|
+
specPath = "";
|
|
72972
|
+
}
|
|
72866
72973
|
}
|
|
72867
|
-
|
|
72974
|
+
if (!specPath) specPath = "api/openapi.yaml";
|
|
72868
72975
|
try {
|
|
72869
|
-
|
|
72976
|
+
gitImpl(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
72870
72977
|
} catch (err) {
|
|
72871
|
-
const msg =
|
|
72872
|
-
logErr(chalk.red(msg));
|
|
72873
|
-
return { ok: false, exitCode: 1, code: "GATE_ERROR", message: msg };
|
|
72874
|
-
}
|
|
72875
|
-
if (!st.isDirectory()) {
|
|
72876
|
-
const msg = `CodeRifts registry-gate: GATE_ERROR \u2014 not a directory: ${rootDir}`;
|
|
72978
|
+
const msg = "CodeRifts publish-gate: GIT_ERROR \u2014 not a git repository (or git unavailable). Cannot resolve before-spec without git. Fail-closed.";
|
|
72877
72979
|
logErr(chalk.red(msg));
|
|
72878
|
-
return {
|
|
72980
|
+
return finish({
|
|
72981
|
+
ok: false,
|
|
72982
|
+
exitCode: 1,
|
|
72983
|
+
code: "GIT_ERROR",
|
|
72984
|
+
message: msg,
|
|
72985
|
+
policy: "fail_closed:git_error"
|
|
72986
|
+
}, { log, json: options.json });
|
|
72879
72987
|
}
|
|
72880
|
-
const
|
|
72881
|
-
|
|
72882
|
-
|
|
72883
|
-
|
|
72884
|
-
|
|
72988
|
+
const beforeRes = resolveBeforeSpec(specPath, {
|
|
72989
|
+
gitImpl,
|
|
72990
|
+
cwd,
|
|
72991
|
+
readFile,
|
|
72992
|
+
packageVersion: deps.packageVersion
|
|
72885
72993
|
});
|
|
72886
|
-
if (!
|
|
72887
|
-
|
|
72888
|
-
|
|
72889
|
-
return {
|
|
72994
|
+
if (!beforeRes.ok) {
|
|
72995
|
+
logErr(chalk.red(`CodeRifts publish-gate: ${beforeRes.code} \u2014 ${beforeRes.message}`));
|
|
72996
|
+
return finish({
|
|
72890
72997
|
ok: false,
|
|
72891
72998
|
exitCode: 1,
|
|
72892
|
-
code:
|
|
72893
|
-
message:
|
|
72894
|
-
|
|
72999
|
+
code: beforeRes.code,
|
|
73000
|
+
message: beforeRes.message,
|
|
73001
|
+
policy: `fail_closed:${beforeRes.code}`,
|
|
73002
|
+
tried: beforeRes.tried
|
|
73003
|
+
}, { log, json: options.json });
|
|
73004
|
+
}
|
|
73005
|
+
const afterRes = resolveAfterSpec(specPath, { cwd, readFile, exists });
|
|
73006
|
+
if (!afterRes.ok) {
|
|
73007
|
+
logErr(chalk.red(`CodeRifts publish-gate: ${afterRes.code} \u2014 ${afterRes.message}`));
|
|
73008
|
+
return finish({
|
|
73009
|
+
ok: false,
|
|
73010
|
+
exitCode: 1,
|
|
73011
|
+
code: afterRes.code,
|
|
73012
|
+
message: afterRes.message,
|
|
73013
|
+
policy: `fail_closed:${afterRes.code}`
|
|
73014
|
+
}, { log, json: options.json });
|
|
73015
|
+
}
|
|
73016
|
+
if (beforeRes.content === afterRes.content) {
|
|
73017
|
+
const payload2 = {
|
|
73018
|
+
ok: true,
|
|
73019
|
+
exitCode: 0,
|
|
73020
|
+
code: "UNCHANGED",
|
|
73021
|
+
message: "Contract artifact unchanged vs baseline; publish permitted.",
|
|
73022
|
+
policy: "permit:unchanged",
|
|
73023
|
+
before_source: beforeRes.source,
|
|
73024
|
+
execution_action: "CONTINUE",
|
|
73025
|
+
receipt: null
|
|
72895
73026
|
};
|
|
73027
|
+
if (!options.json) {
|
|
73028
|
+
log(chalk.green("CodeRifts publish-gate: ALLOW (unchanged)"));
|
|
73029
|
+
log(` baseline: ${beforeRes.source}`);
|
|
73030
|
+
log(` spec: ${specPath}`);
|
|
73031
|
+
}
|
|
73032
|
+
return finish(payload2, { log, json: options.json });
|
|
72896
73033
|
}
|
|
72897
|
-
|
|
72898
|
-
|
|
73034
|
+
let result;
|
|
73035
|
+
try {
|
|
73036
|
+
result = await preflightFn(beforeRes.content, afterRes.content, {
|
|
73037
|
+
apiKey: deps.apiKey,
|
|
73038
|
+
cwd
|
|
73039
|
+
});
|
|
73040
|
+
} catch (err) {
|
|
73041
|
+
const msg = `CodeRifts publish-gate: PREFLIGHT_UNREACHABLE \u2014 ${err && err.message}`;
|
|
72899
73042
|
logErr(chalk.red(msg));
|
|
72900
|
-
return {
|
|
73043
|
+
return finish({
|
|
72901
73044
|
ok: false,
|
|
72902
73045
|
exitCode: 1,
|
|
72903
|
-
code: "
|
|
73046
|
+
code: "PREFLIGHT_UNREACHABLE",
|
|
72904
73047
|
message: msg,
|
|
72905
|
-
|
|
72906
|
-
|
|
73048
|
+
policy: "fail_closed:preflight_unreachable"
|
|
73049
|
+
}, { log, json: options.json });
|
|
73050
|
+
}
|
|
73051
|
+
const perm = evaluatePublishPermission(result);
|
|
73052
|
+
const receipt = extractReceiptRef(result);
|
|
73053
|
+
if (!perm.allow) {
|
|
73054
|
+
const payload2 = {
|
|
73055
|
+
ok: false,
|
|
73056
|
+
exitCode: 1,
|
|
73057
|
+
code: "BLOCK",
|
|
73058
|
+
message: "Publish not permitted by execution_action / decision.",
|
|
73059
|
+
policy: perm.policy,
|
|
73060
|
+
execution_action: perm.execution_action,
|
|
73061
|
+
decision: perm.decision,
|
|
73062
|
+
before_source: beforeRes.source,
|
|
73063
|
+
receipt,
|
|
73064
|
+
fail_policy: "exit_1_on_block_or_resolver_error_or_unreachable"
|
|
72907
73065
|
};
|
|
73066
|
+
if (!options.json) {
|
|
73067
|
+
logErr("");
|
|
73068
|
+
logErr(chalk.red("========================================"));
|
|
73069
|
+
logErr(chalk.red(" CodeRifts: PUBLISH BLOCKED"));
|
|
73070
|
+
logErr(chalk.red("========================================"));
|
|
73071
|
+
logErr(` Policy: ${perm.policy}`);
|
|
73072
|
+
logErr(` execution_action: ${perm.execution_action || "(none)"}`);
|
|
73073
|
+
logErr(` decision: ${perm.decision || "(none)"}`);
|
|
73074
|
+
logErr(` baseline: ${beforeRes.source}`);
|
|
73075
|
+
logErr(` fail policy: exit 1 on BLOCK / resolver error / preflight unreachability`);
|
|
73076
|
+
logErr(chalk.red("========================================"));
|
|
73077
|
+
logErr("");
|
|
73078
|
+
}
|
|
73079
|
+
return finish(payload2, { log, json: options.json });
|
|
72908
73080
|
}
|
|
72909
|
-
const
|
|
72910
|
-
|
|
72911
|
-
|
|
72912
|
-
|
|
72913
|
-
|
|
73081
|
+
const payload = {
|
|
73082
|
+
ok: true,
|
|
73083
|
+
exitCode: 0,
|
|
73084
|
+
code: "ALLOW",
|
|
73085
|
+
message: "Publish permitted.",
|
|
73086
|
+
policy: perm.policy,
|
|
73087
|
+
execution_action: perm.execution_action,
|
|
73088
|
+
decision: perm.decision,
|
|
73089
|
+
before_source: beforeRes.source,
|
|
73090
|
+
receipt
|
|
73091
|
+
};
|
|
73092
|
+
if (!options.json) {
|
|
73093
|
+
log(chalk.green("CodeRifts publish-gate: ALLOW"));
|
|
73094
|
+
log(` Policy: ${perm.policy}`);
|
|
73095
|
+
log(` execution_action: ${perm.execution_action || "(mapped from decision)"}`);
|
|
73096
|
+
log(` baseline: ${beforeRes.source}`);
|
|
73097
|
+
if (receipt) log(` Receipt reference: ${receipt}`);
|
|
73098
|
+
else log(" Receipt reference: (none issued on this path)");
|
|
73099
|
+
}
|
|
73100
|
+
return finish(payload, { log, json: options.json });
|
|
73101
|
+
}
|
|
73102
|
+
function finish(payload, { log, json }) {
|
|
73103
|
+
if (json) {
|
|
73104
|
+
log(JSON.stringify(payload, null, 2));
|
|
73105
|
+
}
|
|
73106
|
+
return payload;
|
|
73107
|
+
}
|
|
73108
|
+
module2.exports = {
|
|
73109
|
+
runPublishGate,
|
|
73110
|
+
resolveBeforeSpec,
|
|
73111
|
+
resolveAfterSpec,
|
|
73112
|
+
evaluatePublishPermission,
|
|
73113
|
+
extractReceiptRef,
|
|
73114
|
+
gitShow,
|
|
73115
|
+
defaultGit,
|
|
73116
|
+
PERMIT_ACTIONS,
|
|
73117
|
+
CLOSED_ACTIONS
|
|
73118
|
+
};
|
|
73119
|
+
}
|
|
73120
|
+
});
|
|
73121
|
+
|
|
73122
|
+
// src/registry-validation-core.js
|
|
73123
|
+
var require_registry_validation_core = __commonJS({
|
|
73124
|
+
"src/registry-validation-core.js"(exports2, module2) {
|
|
73125
|
+
"use strict";
|
|
73126
|
+
var yaml = require_js_yaml();
|
|
73127
|
+
function safeParse(content) {
|
|
73128
|
+
if (!content) return null;
|
|
73129
|
+
try {
|
|
73130
|
+
const trimmed = content.trim();
|
|
73131
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
73132
|
+
return JSON.parse(trimmed);
|
|
73133
|
+
}
|
|
73134
|
+
return yaml.load(trimmed);
|
|
73135
|
+
} catch (_) {
|
|
73136
|
+
return null;
|
|
73137
|
+
}
|
|
73138
|
+
}
|
|
73139
|
+
function validateOpenApiSpec(parsed) {
|
|
73140
|
+
if (!parsed || typeof parsed !== "object") {
|
|
73141
|
+
return { valid: false, error: "Not a valid YAML or JSON object" };
|
|
73142
|
+
}
|
|
73143
|
+
if (parsed.openapi) {
|
|
73144
|
+
const ver = String(parsed.openapi);
|
|
73145
|
+
if (ver.startsWith("3.")) {
|
|
73146
|
+
return { valid: true, version: ver };
|
|
73147
|
+
}
|
|
73148
|
+
return { valid: false, error: `Unsupported OpenAPI version: ${ver}` };
|
|
73149
|
+
}
|
|
73150
|
+
if (parsed.swagger) {
|
|
73151
|
+
const ver = String(parsed.swagger);
|
|
73152
|
+
if (ver.startsWith("2.")) {
|
|
73153
|
+
return { valid: true, version: ver };
|
|
73154
|
+
}
|
|
73155
|
+
return { valid: false, error: `Unsupported Swagger version: ${ver}` };
|
|
73156
|
+
}
|
|
73157
|
+
return { valid: false, error: "Missing required field: 'openapi' or 'swagger'" };
|
|
73158
|
+
}
|
|
73159
|
+
function extractEndpoints(parsed) {
|
|
73160
|
+
const endpoints = [];
|
|
73161
|
+
const paths = parsed.paths || {};
|
|
73162
|
+
const httpMethods = ["get", "post", "put", "patch", "delete", "head", "options"];
|
|
73163
|
+
for (const [path, pathItem] of Object.entries(paths)) {
|
|
73164
|
+
if (!pathItem || typeof pathItem !== "object") continue;
|
|
73165
|
+
for (const method of httpMethods) {
|
|
73166
|
+
if (pathItem[method]) {
|
|
73167
|
+
endpoints.push({
|
|
73168
|
+
path,
|
|
73169
|
+
method: method.toUpperCase(),
|
|
73170
|
+
operationId: pathItem[method].operationId || ""
|
|
73171
|
+
});
|
|
73172
|
+
}
|
|
73173
|
+
}
|
|
73174
|
+
}
|
|
73175
|
+
return endpoints;
|
|
73176
|
+
}
|
|
73177
|
+
function extractSchemaNames(parsed) {
|
|
73178
|
+
const schemas = [];
|
|
73179
|
+
const components = parsed.components?.schemas || {};
|
|
73180
|
+
for (const [name, schema] of Object.entries(components)) {
|
|
73181
|
+
const hash = JSON.stringify(schema);
|
|
73182
|
+
schemas.push({ name, hash });
|
|
73183
|
+
}
|
|
73184
|
+
return schemas;
|
|
73185
|
+
}
|
|
73186
|
+
function extractDefinedScopes(parsed) {
|
|
73187
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
73188
|
+
const schemes = parsed.components?.securitySchemes || {};
|
|
73189
|
+
for (const scheme of Object.values(schemes)) {
|
|
73190
|
+
if (scheme.type === "oauth2" && scheme.flows) {
|
|
73191
|
+
for (const flow of Object.values(scheme.flows)) {
|
|
73192
|
+
if (flow.scopes) {
|
|
73193
|
+
for (const scope of Object.keys(flow.scopes)) {
|
|
73194
|
+
scopes.add(scope);
|
|
73195
|
+
}
|
|
73196
|
+
}
|
|
73197
|
+
}
|
|
73198
|
+
}
|
|
73199
|
+
}
|
|
73200
|
+
return scopes;
|
|
73201
|
+
}
|
|
73202
|
+
function extractUsedScopes(parsed) {
|
|
73203
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
73204
|
+
if (Array.isArray(parsed.security)) {
|
|
73205
|
+
for (const req of parsed.security) {
|
|
73206
|
+
for (const scopeList of Object.values(req)) {
|
|
73207
|
+
if (Array.isArray(scopeList)) {
|
|
73208
|
+
for (const s of scopeList) scopes.add(s);
|
|
73209
|
+
}
|
|
73210
|
+
}
|
|
73211
|
+
}
|
|
73212
|
+
}
|
|
73213
|
+
const paths = parsed.paths || {};
|
|
73214
|
+
const httpMethods = ["get", "post", "put", "patch", "delete", "head", "options"];
|
|
73215
|
+
for (const pathItem of Object.values(paths)) {
|
|
73216
|
+
if (!pathItem || typeof pathItem !== "object") continue;
|
|
73217
|
+
for (const method of httpMethods) {
|
|
73218
|
+
const op = pathItem[method];
|
|
73219
|
+
if (op?.security && Array.isArray(op.security)) {
|
|
73220
|
+
for (const req of op.security) {
|
|
73221
|
+
for (const scopeList of Object.values(req)) {
|
|
73222
|
+
if (Array.isArray(scopeList)) {
|
|
73223
|
+
for (const s of scopeList) scopes.add(s);
|
|
73224
|
+
}
|
|
73225
|
+
}
|
|
73226
|
+
}
|
|
73227
|
+
}
|
|
73228
|
+
}
|
|
73229
|
+
}
|
|
73230
|
+
return scopes;
|
|
73231
|
+
}
|
|
73232
|
+
function extractRefs(obj, refs = /* @__PURE__ */ new Set()) {
|
|
73233
|
+
if (!obj || typeof obj !== "object") return refs;
|
|
73234
|
+
if (Array.isArray(obj)) {
|
|
73235
|
+
for (const item of obj) extractRefs(item, refs);
|
|
73236
|
+
return refs;
|
|
73237
|
+
}
|
|
73238
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
73239
|
+
if (key === "$ref" && typeof value === "string") {
|
|
73240
|
+
refs.add(value);
|
|
73241
|
+
} else {
|
|
73242
|
+
extractRefs(value, refs);
|
|
73243
|
+
}
|
|
73244
|
+
}
|
|
73245
|
+
return refs;
|
|
73246
|
+
}
|
|
73247
|
+
function refResolves(parsed, ref) {
|
|
73248
|
+
if (!ref.startsWith("#/")) return true;
|
|
73249
|
+
const parts = ref.replace("#/", "").split("/");
|
|
73250
|
+
let current = parsed;
|
|
73251
|
+
for (const part of parts) {
|
|
73252
|
+
if (!current || typeof current !== "object") return false;
|
|
73253
|
+
current = current[part];
|
|
73254
|
+
}
|
|
73255
|
+
return current !== void 0;
|
|
73256
|
+
}
|
|
73257
|
+
function validateRegistry(specs) {
|
|
73258
|
+
if (!Array.isArray(specs) || specs.length === 0) {
|
|
73259
|
+
return { valid: true, issues: [], stats: { specs_count: 0, endpoints_count: 0, schemas_count: 0 } };
|
|
73260
|
+
}
|
|
73261
|
+
const issues = [];
|
|
73262
|
+
const parsedSpecs = [];
|
|
73263
|
+
for (const { name, spec } of specs) {
|
|
73264
|
+
const parsed = safeParse(spec);
|
|
73265
|
+
if (!parsed) {
|
|
73266
|
+
issues.push({
|
|
73267
|
+
severity: "error",
|
|
73268
|
+
type: "parse_error",
|
|
73269
|
+
message: `Could not parse spec '${name}' as valid YAML or JSON`,
|
|
73270
|
+
specs: [name]
|
|
73271
|
+
});
|
|
73272
|
+
continue;
|
|
73273
|
+
}
|
|
73274
|
+
const validation = validateOpenApiSpec(parsed);
|
|
73275
|
+
if (!validation.valid) {
|
|
73276
|
+
issues.push({
|
|
73277
|
+
severity: "error",
|
|
73278
|
+
type: "invalid_spec",
|
|
73279
|
+
message: `Spec '${name}' is not a valid OpenAPI document: ${validation.error}`,
|
|
73280
|
+
specs: [name]
|
|
73281
|
+
});
|
|
73282
|
+
continue;
|
|
73283
|
+
}
|
|
73284
|
+
parsedSpecs.push({ name, parsed });
|
|
73285
|
+
}
|
|
73286
|
+
const endpointMap = /* @__PURE__ */ new Map();
|
|
73287
|
+
for (const { name, parsed } of parsedSpecs) {
|
|
73288
|
+
const endpoints = extractEndpoints(parsed);
|
|
73289
|
+
for (const ep of endpoints) {
|
|
73290
|
+
const key = `${ep.method} ${ep.path}`;
|
|
73291
|
+
if (!endpointMap.has(key)) endpointMap.set(key, []);
|
|
73292
|
+
endpointMap.get(key).push({ spec: name, operationId: ep.operationId });
|
|
73293
|
+
}
|
|
73294
|
+
}
|
|
73295
|
+
for (const [endpoint, owners] of endpointMap) {
|
|
73296
|
+
if (owners.length > 1) {
|
|
73297
|
+
const specNames = owners.map((o) => o.spec);
|
|
73298
|
+
issues.push({
|
|
73299
|
+
severity: "warning",
|
|
73300
|
+
type: "endpoint_collision",
|
|
73301
|
+
message: `Endpoint '${endpoint}' is defined in multiple specs: ${specNames.join(", ")}`,
|
|
73302
|
+
specs: specNames
|
|
73303
|
+
});
|
|
73304
|
+
}
|
|
73305
|
+
}
|
|
73306
|
+
const schemaMap = /* @__PURE__ */ new Map();
|
|
73307
|
+
for (const { name, parsed } of parsedSpecs) {
|
|
73308
|
+
const schemas = extractSchemaNames(parsed);
|
|
73309
|
+
for (const s of schemas) {
|
|
73310
|
+
if (!schemaMap.has(s.name)) schemaMap.set(s.name, []);
|
|
73311
|
+
schemaMap.get(s.name).push({ spec: name, hash: s.hash });
|
|
73312
|
+
}
|
|
73313
|
+
}
|
|
73314
|
+
for (const [schemaName, definitions] of schemaMap) {
|
|
73315
|
+
if (definitions.length > 1) {
|
|
73316
|
+
const uniqueHashes = new Set(definitions.map((d) => d.hash));
|
|
73317
|
+
if (uniqueHashes.size > 1) {
|
|
73318
|
+
const specNames = definitions.map((d) => d.spec);
|
|
73319
|
+
issues.push({
|
|
73320
|
+
severity: "warning",
|
|
73321
|
+
type: "schema_conflict",
|
|
73322
|
+
message: `Schema '${schemaName}' has conflicting definitions across specs: ${specNames.join(", ")}`,
|
|
73323
|
+
specs: specNames
|
|
73324
|
+
});
|
|
73325
|
+
}
|
|
73326
|
+
}
|
|
73327
|
+
}
|
|
73328
|
+
for (const { name, parsed } of parsedSpecs) {
|
|
73329
|
+
const defined = extractDefinedScopes(parsed);
|
|
73330
|
+
const used = extractUsedScopes(parsed);
|
|
73331
|
+
for (const scope of used) {
|
|
73332
|
+
if (!defined.has(scope)) {
|
|
73333
|
+
issues.push({
|
|
73334
|
+
severity: "warning",
|
|
73335
|
+
type: "undefined_scope",
|
|
73336
|
+
message: `Scope '${scope}' is used in '${name}' but not defined in securitySchemes`,
|
|
73337
|
+
specs: [name]
|
|
73338
|
+
});
|
|
73339
|
+
}
|
|
73340
|
+
}
|
|
73341
|
+
for (const scope of defined) {
|
|
73342
|
+
if (!used.has(scope)) {
|
|
73343
|
+
issues.push({
|
|
73344
|
+
severity: "info",
|
|
73345
|
+
type: "unused_scope",
|
|
73346
|
+
message: `Scope '${scope}' is defined in '${name}' but never used in any operation`,
|
|
73347
|
+
specs: [name]
|
|
73348
|
+
});
|
|
73349
|
+
}
|
|
73350
|
+
}
|
|
73351
|
+
}
|
|
73352
|
+
for (const { name, parsed } of parsedSpecs) {
|
|
73353
|
+
const refs = extractRefs(parsed);
|
|
73354
|
+
for (const ref of refs) {
|
|
73355
|
+
if (!refResolves(parsed, ref)) {
|
|
73356
|
+
issues.push({
|
|
73357
|
+
severity: "error",
|
|
73358
|
+
type: "unresolved_ref",
|
|
73359
|
+
message: `$ref '${ref}' in '${name}' does not resolve`,
|
|
73360
|
+
specs: [name]
|
|
73361
|
+
});
|
|
73362
|
+
}
|
|
73363
|
+
}
|
|
73364
|
+
}
|
|
73365
|
+
let totalEndpoints = 0;
|
|
73366
|
+
let totalSchemas = 0;
|
|
73367
|
+
for (const { parsed } of parsedSpecs) {
|
|
73368
|
+
totalEndpoints += extractEndpoints(parsed).length;
|
|
73369
|
+
totalSchemas += extractSchemaNames(parsed).length;
|
|
73370
|
+
}
|
|
73371
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
73372
|
+
return {
|
|
73373
|
+
valid: !hasErrors,
|
|
73374
|
+
issues,
|
|
73375
|
+
stats: {
|
|
73376
|
+
specs_count: parsedSpecs.length,
|
|
73377
|
+
endpoints_count: totalEndpoints,
|
|
73378
|
+
schemas_count: totalSchemas
|
|
73379
|
+
}
|
|
73380
|
+
};
|
|
73381
|
+
}
|
|
73382
|
+
module2.exports = {
|
|
73383
|
+
validateRegistry,
|
|
73384
|
+
safeParse,
|
|
73385
|
+
validateOpenApiSpec,
|
|
73386
|
+
// Exported for testing
|
|
73387
|
+
extractEndpoints,
|
|
73388
|
+
extractSchemaNames,
|
|
73389
|
+
extractDefinedScopes,
|
|
73390
|
+
extractUsedScopes,
|
|
73391
|
+
extractRefs,
|
|
73392
|
+
refResolves
|
|
73393
|
+
};
|
|
73394
|
+
}
|
|
73395
|
+
});
|
|
73396
|
+
|
|
73397
|
+
// src/commands/registry-gate.js
|
|
73398
|
+
var require_registry_gate = __commonJS({
|
|
73399
|
+
"src/commands/registry-gate.js"(exports2, module2) {
|
|
73400
|
+
"use strict";
|
|
73401
|
+
var fs = require("fs");
|
|
73402
|
+
var path = require("path");
|
|
73403
|
+
var chalk = require_source();
|
|
73404
|
+
var { matchGlob } = require_cjs3();
|
|
73405
|
+
var {
|
|
73406
|
+
validateRegistry,
|
|
73407
|
+
safeParse,
|
|
73408
|
+
validateOpenApiSpec
|
|
73409
|
+
} = require_registry_validation_core();
|
|
73410
|
+
if (process.env.NO_COLOR) chalk.level = 0;
|
|
73411
|
+
var SPEC_EXT = /* @__PURE__ */ new Set([".yaml", ".yml", ".json"]);
|
|
73412
|
+
function walkSpecCandidates(rootDir, {
|
|
73413
|
+
readdirSync = fs.readdirSync.bind(fs),
|
|
73414
|
+
statSync = fs.statSync.bind(fs)
|
|
73415
|
+
} = {}) {
|
|
73416
|
+
const out = [];
|
|
73417
|
+
function walk(absDir) {
|
|
73418
|
+
let entries;
|
|
73419
|
+
try {
|
|
73420
|
+
entries = readdirSync(absDir, { withFileTypes: true });
|
|
73421
|
+
} catch (err) {
|
|
73422
|
+
const e = new Error(`Cannot read directory ${absDir}: ${err && err.message}`);
|
|
73423
|
+
e.code = "GATE_ERROR";
|
|
73424
|
+
e.path = absDir;
|
|
73425
|
+
throw e;
|
|
73426
|
+
}
|
|
73427
|
+
for (const ent of entries) {
|
|
73428
|
+
const name = ent.name;
|
|
73429
|
+
if (name === "node_modules" || name.startsWith(".")) continue;
|
|
73430
|
+
const abs = path.join(absDir, name);
|
|
73431
|
+
let isDir = ent.isDirectory && ent.isDirectory();
|
|
73432
|
+
let isFile = ent.isFile && ent.isFile();
|
|
73433
|
+
if (!isDir && !isFile) {
|
|
73434
|
+
try {
|
|
73435
|
+
const st = statSync(abs);
|
|
73436
|
+
isDir = st.isDirectory();
|
|
73437
|
+
isFile = st.isFile();
|
|
73438
|
+
} catch (err) {
|
|
73439
|
+
const e = new Error(`Cannot stat ${abs}: ${err && err.message}`);
|
|
73440
|
+
e.code = "GATE_ERROR";
|
|
73441
|
+
e.path = abs;
|
|
73442
|
+
throw e;
|
|
73443
|
+
}
|
|
73444
|
+
}
|
|
73445
|
+
if (isDir) {
|
|
73446
|
+
walk(abs);
|
|
73447
|
+
continue;
|
|
73448
|
+
}
|
|
73449
|
+
if (isFile) {
|
|
73450
|
+
const ext = path.extname(name).toLowerCase();
|
|
73451
|
+
if (SPEC_EXT.has(ext)) out.push(abs);
|
|
73452
|
+
}
|
|
73453
|
+
}
|
|
73454
|
+
}
|
|
73455
|
+
walk(rootDir);
|
|
73456
|
+
return out.sort();
|
|
73457
|
+
}
|
|
73458
|
+
function relPosix(rootDir, absPath) {
|
|
73459
|
+
let rel = path.relative(rootDir, absPath);
|
|
73460
|
+
if (path.sep !== "/") rel = rel.split(path.sep).join("/");
|
|
73461
|
+
return rel;
|
|
73462
|
+
}
|
|
73463
|
+
function readFileThreeState(absPath, readFileSync = fs.readFileSync.bind(fs)) {
|
|
73464
|
+
try {
|
|
73465
|
+
const content = readFileSync(absPath, "utf8");
|
|
73466
|
+
return { ok: true, content: content == null ? "" : String(content) };
|
|
73467
|
+
} catch (err) {
|
|
73468
|
+
const msg = err && err.message ? String(err.message) : String(err);
|
|
73469
|
+
return {
|
|
73470
|
+
ok: false,
|
|
73471
|
+
code: "GATE_ERROR",
|
|
73472
|
+
message: `unreadable file: ${absPath} (${msg.slice(0, 200)})`,
|
|
73473
|
+
path: absPath
|
|
73474
|
+
};
|
|
73475
|
+
}
|
|
73476
|
+
}
|
|
73477
|
+
function discoverRegistrySpecs(dir, {
|
|
73478
|
+
glob = null,
|
|
73479
|
+
readdirSync = fs.readdirSync.bind(fs),
|
|
73480
|
+
readFileSync = fs.readFileSync.bind(fs),
|
|
73481
|
+
statSync = fs.statSync.bind(fs)
|
|
73482
|
+
} = {}) {
|
|
73483
|
+
const rootDir = path.resolve(dir);
|
|
73484
|
+
let candidates;
|
|
73485
|
+
try {
|
|
73486
|
+
candidates = walkSpecCandidates(rootDir, { readdirSync, statSync });
|
|
73487
|
+
} catch (err) {
|
|
73488
|
+
return {
|
|
73489
|
+
ok: false,
|
|
73490
|
+
code: err.code || "GATE_ERROR",
|
|
73491
|
+
message: err.message || String(err),
|
|
73492
|
+
path: err.path
|
|
73493
|
+
};
|
|
73494
|
+
}
|
|
73495
|
+
if (glob) {
|
|
73496
|
+
candidates = candidates.filter((abs) => matchGlob(glob, relPosix(rootDir, abs)));
|
|
73497
|
+
}
|
|
73498
|
+
const specs = [];
|
|
73499
|
+
let skippedNonSpec = 0;
|
|
73500
|
+
for (const abs of candidates) {
|
|
73501
|
+
const read = readFileThreeState(abs, readFileSync);
|
|
73502
|
+
if (!read.ok) {
|
|
73503
|
+
return {
|
|
73504
|
+
ok: false,
|
|
73505
|
+
code: "GATE_ERROR",
|
|
73506
|
+
message: read.message,
|
|
73507
|
+
path: read.path || abs
|
|
73508
|
+
};
|
|
73509
|
+
}
|
|
73510
|
+
const name = relPosix(rootDir, abs);
|
|
73511
|
+
const parsed = safeParse(read.content);
|
|
73512
|
+
if (!parsed || typeof parsed !== "object") {
|
|
73513
|
+
specs.push({ name, spec: read.content });
|
|
73514
|
+
continue;
|
|
73515
|
+
}
|
|
73516
|
+
const shape = validateOpenApiSpec(parsed);
|
|
73517
|
+
if (!shape.valid) {
|
|
73518
|
+
if (shape.error && shape.error.includes("Missing required field: 'openapi' or 'swagger'")) {
|
|
73519
|
+
skippedNonSpec += 1;
|
|
73520
|
+
continue;
|
|
73521
|
+
}
|
|
73522
|
+
specs.push({ name, spec: read.content });
|
|
73523
|
+
continue;
|
|
73524
|
+
}
|
|
73525
|
+
specs.push({ name, spec: read.content });
|
|
73526
|
+
}
|
|
73527
|
+
return {
|
|
73528
|
+
ok: true,
|
|
73529
|
+
specs,
|
|
73530
|
+
skippedNonSpec,
|
|
73531
|
+
candidates: candidates.length,
|
|
73532
|
+
rootDir
|
|
73533
|
+
};
|
|
73534
|
+
}
|
|
73535
|
+
function findingsFailGate(issues, { warnOnly = false, errorsOnly = false } = {}) {
|
|
73536
|
+
if (warnOnly) return false;
|
|
73537
|
+
for (const i of issues || []) {
|
|
73538
|
+
if (i.severity === "error") return true;
|
|
73539
|
+
if (i.severity === "warning" && !errorsOnly) return true;
|
|
73540
|
+
}
|
|
73541
|
+
return false;
|
|
73542
|
+
}
|
|
73543
|
+
function countBySeverity(issues) {
|
|
73544
|
+
const c = { error: 0, warning: 0, info: 0 };
|
|
73545
|
+
for (const i of issues || []) {
|
|
73546
|
+
if (i.severity === "error") c.error += 1;
|
|
73547
|
+
else if (i.severity === "warning") c.warning += 1;
|
|
73548
|
+
else if (i.severity === "info") c.info += 1;
|
|
73549
|
+
}
|
|
73550
|
+
return c;
|
|
73551
|
+
}
|
|
73552
|
+
function severityColor(sev) {
|
|
73553
|
+
if (sev === "error") return chalk.red;
|
|
73554
|
+
if (sev === "warning") return chalk.yellow;
|
|
73555
|
+
return chalk.cyan;
|
|
73556
|
+
}
|
|
73557
|
+
function printFindings(issues, log) {
|
|
73558
|
+
for (const i of issues || []) {
|
|
73559
|
+
const color = severityColor(i.severity);
|
|
73560
|
+
const files = Array.isArray(i.specs) ? i.specs.join(", ") : "";
|
|
73561
|
+
log(color(`[${i.severity}] ${i.type}`) + (files ? chalk.dim(` ${files}`) : ""));
|
|
73562
|
+
log(` ${i.message}`);
|
|
73563
|
+
}
|
|
73564
|
+
}
|
|
73565
|
+
function runRegistryGate(options = {}, deps = {}) {
|
|
73566
|
+
const log = deps.log || console.log.bind(console);
|
|
73567
|
+
const logErr = deps.logErr || console.error.bind(console);
|
|
73568
|
+
const cwd = deps.cwd || process.cwd();
|
|
73569
|
+
const dirArg = options.dir != null && options.dir !== "" ? options.dir : ".";
|
|
73570
|
+
const rootDir = path.isAbsolute(dirArg) ? dirArg : path.resolve(cwd, dirArg);
|
|
73571
|
+
const readdirSync = deps.readdirSync || fs.readdirSync.bind(fs);
|
|
73572
|
+
const readFileSync = deps.readFileSync || fs.readFileSync.bind(fs);
|
|
73573
|
+
const statSync = deps.statSync || fs.statSync.bind(fs);
|
|
73574
|
+
const existsSync = deps.existsSync || fs.existsSync.bind(fs);
|
|
73575
|
+
if (!existsSync(rootDir)) {
|
|
73576
|
+
const msg = `CodeRifts registry-gate: GATE_ERROR \u2014 directory not found: ${rootDir}`;
|
|
73577
|
+
logErr(chalk.red(msg));
|
|
73578
|
+
return { ok: false, exitCode: 1, code: "GATE_ERROR", message: msg };
|
|
73579
|
+
}
|
|
73580
|
+
let st;
|
|
73581
|
+
try {
|
|
73582
|
+
st = statSync(rootDir);
|
|
73583
|
+
} catch (err) {
|
|
73584
|
+
const msg = `CodeRifts registry-gate: GATE_ERROR \u2014 cannot access ${rootDir}: ${err && err.message}`;
|
|
73585
|
+
logErr(chalk.red(msg));
|
|
73586
|
+
return { ok: false, exitCode: 1, code: "GATE_ERROR", message: msg };
|
|
73587
|
+
}
|
|
73588
|
+
if (!st.isDirectory()) {
|
|
73589
|
+
const msg = `CodeRifts registry-gate: GATE_ERROR \u2014 not a directory: ${rootDir}`;
|
|
73590
|
+
logErr(chalk.red(msg));
|
|
73591
|
+
return { ok: false, exitCode: 1, code: "GATE_ERROR", message: msg };
|
|
73592
|
+
}
|
|
73593
|
+
const discovered = discoverRegistrySpecs(rootDir, {
|
|
73594
|
+
glob: options.glob || null,
|
|
73595
|
+
readdirSync,
|
|
73596
|
+
readFileSync,
|
|
73597
|
+
statSync
|
|
73598
|
+
});
|
|
73599
|
+
if (!discovered.ok) {
|
|
73600
|
+
const msg = `CodeRifts registry-gate: ${discovered.code} \u2014 ${discovered.message}`;
|
|
73601
|
+
logErr(chalk.red(msg));
|
|
73602
|
+
return {
|
|
73603
|
+
ok: false,
|
|
73604
|
+
exitCode: 1,
|
|
73605
|
+
code: discovered.code || "GATE_ERROR",
|
|
73606
|
+
message: discovered.message,
|
|
73607
|
+
path: discovered.path
|
|
73608
|
+
};
|
|
73609
|
+
}
|
|
73610
|
+
if (discovered.specs.length === 0) {
|
|
73611
|
+
const msg = `CodeRifts registry-gate: REGISTRY_EMPTY \u2014 no OpenAPI/Swagger specs discovered (scanned ${discovered.candidates} yaml/json file(s), skipped non-spec: ${discovered.skippedNonSpec}). An admission gate guarding nothing must fail, not pass.`;
|
|
73612
|
+
logErr(chalk.red(msg));
|
|
73613
|
+
return {
|
|
73614
|
+
ok: false,
|
|
73615
|
+
exitCode: 1,
|
|
73616
|
+
code: "REGISTRY_EMPTY",
|
|
73617
|
+
message: msg,
|
|
73618
|
+
skippedNonSpec: discovered.skippedNonSpec,
|
|
73619
|
+
candidates: discovered.candidates
|
|
73620
|
+
};
|
|
73621
|
+
}
|
|
73622
|
+
const result = validateRegistry(discovered.specs);
|
|
73623
|
+
const counts = countBySeverity(result.issues);
|
|
73624
|
+
const mode = {
|
|
73625
|
+
warnOnly: !!options.warnOnly,
|
|
73626
|
+
errorsOnly: !!options.errorsOnly
|
|
72914
73627
|
};
|
|
72915
73628
|
const fail = findingsFailGate(result.issues, mode);
|
|
72916
73629
|
if (result.issues.length > 0) {
|
|
@@ -99945,7 +100658,7 @@ var require_enforce = __commonJS({
|
|
|
99945
100658
|
var DEPLOY_GUIDANCE = [
|
|
99946
100659
|
"Deploy is declared-only on the server (never server-observed ENFORCING).",
|
|
99947
100660
|
"Add a CD step that runs: coderifts deploy-gate --env <env> --artifact <id> --receipt <file> --enforce",
|
|
99948
|
-
" (
|
|
100661
|
+
" (fail-closed by default; --enforce attests ENFORCING for inescapable_deploy. CODERIFTS_DEPLOY_ADVISORY=1 to soften.)",
|
|
99949
100662
|
"Also set policy.require_source_binding: true in .coderifts.yml for the deploy declaration leg."
|
|
99950
100663
|
].join("\n");
|
|
99951
100664
|
var CONTENT_GUIDANCE = [
|
|
@@ -101123,834 +101836,196 @@ var require_adopt = __commonJS({
|
|
|
101123
101836
|
}
|
|
101124
101837
|
}
|
|
101125
101838
|
lines.push("");
|
|
101126
|
-
lines.push(chalk.dim("High fire_count \u2192 candidate false-positive noise (not safe to default-block without review)."));
|
|
101127
|
-
lines.push(chalk.dim("Rare + CRITICAL/HIGH \u2192 safer default-block candidates. Human decides; no auto-flip."));
|
|
101128
|
-
lines.push("");
|
|
101129
|
-
return lines.join("\n");
|
|
101130
|
-
}
|
|
101131
|
-
function pad(s, n) {
|
|
101132
|
-
const t = String(s || "");
|
|
101133
|
-
if (t.length >= n) return `${t.slice(0, n - 1)} `;
|
|
101134
|
-
return t + " ".repeat(n - t.length);
|
|
101135
|
-
}
|
|
101136
|
-
async function runAdopt(options = {}, deps = {}) {
|
|
101137
|
-
const log = deps.log || console.log.bind(console);
|
|
101138
|
-
const logErr = deps.logErr || console.error.bind(console);
|
|
101139
|
-
const load = deps.loadCore || loadCore;
|
|
101140
|
-
const collected = collectChangeSetsFromGit(options, deps);
|
|
101141
|
-
if (!collected.ok) {
|
|
101142
|
-
logErr(chalk.red(`CodeRifts adopt: ${collected.code} \u2014 ${collected.message}`));
|
|
101143
|
-
return { ok: false, exitCode: 1, code: collected.code, message: collected.message };
|
|
101144
|
-
}
|
|
101145
|
-
let core;
|
|
101146
|
-
try {
|
|
101147
|
-
core = load();
|
|
101148
|
-
} catch (err) {
|
|
101149
|
-
logErr(chalk.red(`CodeRifts adopt: ${err.message}`));
|
|
101150
|
-
return { ok: false, exitCode: 1, code: "CORE_LOAD_ERROR", message: err.message };
|
|
101151
|
-
}
|
|
101152
|
-
const runCore = () => core.buildCounterfactualReport(collected.changeSets, deps.config || {});
|
|
101153
|
-
const report = options.json ? await withJsonQuietAnalyzerLogs(runCore) : await runCore();
|
|
101154
|
-
if (options.json) {
|
|
101155
|
-
log(JSON.stringify({
|
|
101156
|
-
...report,
|
|
101157
|
-
meta: {
|
|
101158
|
-
commit_count: collected.commit_count,
|
|
101159
|
-
change_set_count: collected.changeSets.length
|
|
101160
|
-
}
|
|
101161
|
-
}, null, 2));
|
|
101162
|
-
} else {
|
|
101163
|
-
log(renderHuman(report, { commit_count: collected.commit_count }));
|
|
101164
|
-
}
|
|
101165
|
-
return {
|
|
101166
|
-
ok: true,
|
|
101167
|
-
exitCode: 0,
|
|
101168
|
-
report,
|
|
101169
|
-
meta: { commit_count: collected.commit_count }
|
|
101170
|
-
};
|
|
101171
|
-
}
|
|
101172
|
-
module2.exports = {
|
|
101173
|
-
runAdopt,
|
|
101174
|
-
collectChangeSetsFromGit,
|
|
101175
|
-
isContractPath,
|
|
101176
|
-
withJsonQuietAnalyzerLogs,
|
|
101177
|
-
renderHuman,
|
|
101178
|
-
defaultGit,
|
|
101179
|
-
SOURCE_CODE_EXT_RE
|
|
101180
|
-
};
|
|
101181
|
-
}
|
|
101182
|
-
});
|
|
101183
|
-
|
|
101184
|
-
// src/commands/outcome.js
|
|
101185
|
-
var require_outcome = __commonJS({
|
|
101186
|
-
"src/commands/outcome.js"(exports2, module2) {
|
|
101187
|
-
"use strict";
|
|
101188
|
-
var chalk = require_source();
|
|
101189
|
-
var { getApiKey } = require_config();
|
|
101190
|
-
var { cloudPostOutcome } = require_cloud();
|
|
101191
|
-
if (process.env.NO_COLOR) chalk.level = 0;
|
|
101192
|
-
var OUTCOME_KINDS = Object.freeze([
|
|
101193
|
-
"deploy_succeeded",
|
|
101194
|
-
"deploy_failed",
|
|
101195
|
-
"rolled_back",
|
|
101196
|
-
"consumer_break_reported",
|
|
101197
|
-
"remediation_verified_working",
|
|
101198
|
-
"false_positive_reported",
|
|
101199
|
-
"other_reported"
|
|
101200
|
-
]);
|
|
101201
|
-
var OUTCOME_KIND_SET = new Set(OUTCOME_KINDS);
|
|
101202
|
-
var USAGE = [
|
|
101203
|
-
"Usage: coderifts outcome <kind> --decision <decision_id> [--observed-at <ISO>] [--details <json>] [--json]",
|
|
101204
|
-
"",
|
|
101205
|
-
"Report a post-hoc observed outcome for a past decision_id to the CodeRifts cloud API.",
|
|
101206
|
-
"This records the CALLER'S assertion (e.g. your deploy job knows success/failure) \u2014",
|
|
101207
|
-
"the command does NOT itself verify that a deploy succeeded or failed.",
|
|
101208
|
-
"",
|
|
101209
|
-
"kind (required, closed set):",
|
|
101210
|
-
` ${OUTCOME_KINDS.join(", ")}`,
|
|
101211
|
-
"",
|
|
101212
|
-
"Requires a cloud API key (coderifts login or CODERIFTS_API_KEY).",
|
|
101213
|
-
"POST /api/v1/outcomes \u2014 source is always reported (server-side); reporter is derived from the key."
|
|
101214
|
-
].join("\n");
|
|
101215
|
-
function isValidOutcomeKind(kind) {
|
|
101216
|
-
return typeof kind === "string" && OUTCOME_KIND_SET.has(kind);
|
|
101217
|
-
}
|
|
101218
|
-
function isValidObservedAt(iso) {
|
|
101219
|
-
if (typeof iso !== "string" || !iso.trim()) return false;
|
|
101220
|
-
const t = Date.parse(iso);
|
|
101221
|
-
return Number.isFinite(t);
|
|
101222
|
-
}
|
|
101223
|
-
function parseDetails(raw) {
|
|
101224
|
-
if (raw == null || raw === "") return { ok: true, details: null };
|
|
101225
|
-
if (typeof raw !== "string") return { ok: false, error: "--details must be a JSON string" };
|
|
101226
|
-
try {
|
|
101227
|
-
const v = JSON.parse(raw);
|
|
101228
|
-
if (v !== null && typeof v !== "object") {
|
|
101229
|
-
return { ok: false, error: "--details must be a JSON object or array" };
|
|
101230
|
-
}
|
|
101231
|
-
return { ok: true, details: v };
|
|
101232
|
-
} catch (e) {
|
|
101233
|
-
return { ok: false, error: `--details is not valid JSON: ${e && e.message || "parse error"}` };
|
|
101234
|
-
}
|
|
101235
|
-
}
|
|
101236
|
-
async function runOutcome(kind, options = {}, deps = {}) {
|
|
101237
|
-
const getKey = deps.getApiKey || getApiKey;
|
|
101238
|
-
const postOutcome = deps.cloudPostOutcome || cloudPostOutcome;
|
|
101239
|
-
const log = deps.log || console.log;
|
|
101240
|
-
const errLog = deps.errLog || console.error;
|
|
101241
|
-
const nowIso = deps.nowIso || (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
101242
|
-
if (kind == null || String(kind).trim() === "") {
|
|
101243
|
-
errLog(chalk.red("Error: missing outcome kind"));
|
|
101244
|
-
errLog(USAGE);
|
|
101245
|
-
return { exitCode: 1, error: "missing_kind" };
|
|
101246
|
-
}
|
|
101247
|
-
const outcomeKind = String(kind).trim();
|
|
101248
|
-
if (!isValidOutcomeKind(outcomeKind)) {
|
|
101249
|
-
errLog(chalk.red(`Error: invalid outcome kind '${outcomeKind}'`));
|
|
101250
|
-
errLog(chalk.dim(` Valid kinds: ${OUTCOME_KINDS.join(", ")}`));
|
|
101251
|
-
return { exitCode: 1, error: "invalid_kind" };
|
|
101252
|
-
}
|
|
101253
|
-
const decisionId = options.decision != null ? String(options.decision).trim() : "";
|
|
101254
|
-
if (!decisionId) {
|
|
101255
|
-
errLog(chalk.red("Error: --decision <decision_id> is required"));
|
|
101256
|
-
errLog(USAGE);
|
|
101257
|
-
return { exitCode: 1, error: "missing_decision" };
|
|
101258
|
-
}
|
|
101259
|
-
let observedAt;
|
|
101260
|
-
if (options.observedAt != null && String(options.observedAt).trim() !== "") {
|
|
101261
|
-
observedAt = String(options.observedAt).trim();
|
|
101262
|
-
if (!isValidObservedAt(observedAt)) {
|
|
101263
|
-
errLog(chalk.red("Error: --observed-at must be a valid ISO-8601 timestamp"));
|
|
101264
|
-
return { exitCode: 1, error: "invalid_observed_at" };
|
|
101265
|
-
}
|
|
101266
|
-
} else {
|
|
101267
|
-
observedAt = nowIso();
|
|
101268
|
-
}
|
|
101269
|
-
const det = parseDetails(options.details);
|
|
101270
|
-
if (!det.ok) {
|
|
101271
|
-
errLog(chalk.red(`Error: ${det.error}`));
|
|
101272
|
-
return { exitCode: 1, error: "invalid_details" };
|
|
101273
|
-
}
|
|
101274
|
-
const apiKey = getKey();
|
|
101275
|
-
if (!apiKey) {
|
|
101276
|
-
errLog(chalk.red("Error: no API key. Run `coderifts login` or set CODERIFTS_API_KEY."));
|
|
101277
|
-
return { exitCode: 1, error: "missing_api_key" };
|
|
101278
|
-
}
|
|
101279
|
-
const body = {
|
|
101280
|
-
decision_id: decisionId,
|
|
101281
|
-
outcome_kind: outcomeKind,
|
|
101282
|
-
observed_at: observedAt
|
|
101283
|
-
};
|
|
101284
|
-
if (det.details != null) body.details = det.details;
|
|
101285
|
-
let response;
|
|
101286
|
-
try {
|
|
101287
|
-
response = await postOutcome(apiKey, body);
|
|
101288
|
-
} catch (e) {
|
|
101289
|
-
const msg = e && e.message ? String(e.message) : "request failed";
|
|
101290
|
-
errLog(chalk.red(`Error: ${msg}`));
|
|
101291
|
-
if (e && e.statusCode) errLog(chalk.dim(` (HTTP ${e.statusCode})`));
|
|
101292
|
-
if (e && e.code) errLog(chalk.dim(` (${e.code})`));
|
|
101293
|
-
return { exitCode: 1, error: msg, body };
|
|
101294
|
-
}
|
|
101295
|
-
if (options.json) {
|
|
101296
|
-
log(JSON.stringify(response, null, 2));
|
|
101297
|
-
} else {
|
|
101298
|
-
const o = response && response.outcome ? response.outcome : response;
|
|
101299
|
-
log(chalk.bold("CodeRifts outcome recorded"));
|
|
101300
|
-
log(` kind: ${outcomeKind}`);
|
|
101301
|
-
log(` decision_id: ${decisionId}`);
|
|
101302
|
-
log(` observed_at: ${observedAt}`);
|
|
101303
|
-
if (o && o.id) log(` id: ${o.id}`);
|
|
101304
|
-
if (response && response.source) log(` source: ${response.source}`);
|
|
101305
|
-
log(chalk.dim(" Caller assertion only \u2014 this command did not verify the deploy."));
|
|
101306
|
-
}
|
|
101307
|
-
return { exitCode: 0, response, body };
|
|
101308
|
-
}
|
|
101309
|
-
module2.exports = {
|
|
101310
|
-
runOutcome,
|
|
101311
|
-
isValidOutcomeKind,
|
|
101312
|
-
isValidObservedAt,
|
|
101313
|
-
parseDetails,
|
|
101314
|
-
OUTCOME_KINDS,
|
|
101315
|
-
USAGE
|
|
101316
|
-
};
|
|
101317
|
-
}
|
|
101318
|
-
});
|
|
101319
|
-
|
|
101320
|
-
// src/commands/claude-hook.js
|
|
101321
|
-
var require_claude_hook = __commonJS({
|
|
101322
|
-
"src/commands/claude-hook.js"(exports2, module2) {
|
|
101323
|
-
"use strict";
|
|
101324
|
-
var fs = require("fs");
|
|
101325
|
-
var path = require("path");
|
|
101326
|
-
var { execSync } = require("child_process");
|
|
101327
|
-
var { getApiKey } = require_config();
|
|
101328
|
-
var { cloudAuthorizePreflight } = require_cloud();
|
|
101329
|
-
var DEFAULT_SPEC_PATH = "api/openapi.yaml";
|
|
101330
|
-
var CLOSED_ACTIONS = /* @__PURE__ */ new Set([
|
|
101331
|
-
"CONTINUE",
|
|
101332
|
-
"CONTINUE_WITH_MONITORING",
|
|
101333
|
-
"REQUEST_APPROVAL",
|
|
101334
|
-
"STOP"
|
|
101335
|
-
]);
|
|
101336
|
-
var USAGE = [
|
|
101337
|
-
"Usage: coderifts claude-hook",
|
|
101338
|
-
"",
|
|
101339
|
-
"Claude Code PreToolUse hook: gates Write|Edit|MultiEdit when they touch the",
|
|
101340
|
-
"configured contract spec path. Reads JSON context from STDIN.",
|
|
101341
|
-
"",
|
|
101342
|
-
"Exit map (Claude Code semantics \u2014 get this exactly right):",
|
|
101343
|
-
" 2 BLOCK \u2014 tool call cancelled (BLOCK/STOP, REQUEST_APPROVAL, unrecognised action,",
|
|
101344
|
-
" governance could not run, CONTINUE_WITH_MONITORING without a wired sink)",
|
|
101345
|
-
" 0 allow \u2014 CONTINUE, or CWM with a host-asserted monitoring sink, or non-contract skip",
|
|
101346
|
-
" 1 NEVER used for security deny (Claude treats exit 1 as non-blocking; action proceeds)",
|
|
101347
|
-
"",
|
|
101348
|
-
"Fail-closed DEFAULT (exit 2, reason enforce_indeterminate) when governance could not run:",
|
|
101349
|
-
" no API key / API unreachable / disk unreadable / edit-apply failure on the spec path",
|
|
101350
|
-
" Absence of a key is not permission. Explicit opt-out: CODERIFTS_ADVISORY=1|true.",
|
|
101351
|
-
"",
|
|
101352
|
-
"Parse-gap DEFAULT (exit 2): unparseable stdin / missing file_path \u2014 refusing (fail-closed);",
|
|
101353
|
-
" set CODERIFTS_ADVISORY=1 to soften. JSONL hook_blocked cause stdin_unparseable|missing_file_path.",
|
|
101354
|
-
"",
|
|
101355
|
-
"Still exit 0 (nothing to govern): non-spec path, identical content.",
|
|
101356
|
-
"",
|
|
101357
|
-
"CONTINUE_WITH_MONITORING: allow only if the host asserts a sink",
|
|
101358
|
-
" (CODERIFTS_MONITORING_SINK_WIRED=1|true or git config coderifts.monitoringSinkWired).",
|
|
101359
|
-
" Host claim \u2014 not delivery proof (same class as GuardConfig.monitoringSinkWired).",
|
|
101360
|
-
"",
|
|
101361
|
-
"CODERIFTS_STRICT=1|true: same fail-closed as default; cannot be weakened by ADVISORY.",
|
|
101362
|
-
"",
|
|
101363
|
-
"Spec path (same source as git pre-push hook):",
|
|
101364
|
-
" git config coderifts.specPath (default: api/openapi.yaml)",
|
|
101365
|
-
"",
|
|
101366
|
-
"Baseline at PreToolUse: before = current file on disk (pre-edit state \u2014 the tool has",
|
|
101367
|
-
"not written yet). after = proposed content from tool_input (Write: content; Edit/MultiEdit:",
|
|
101368
|
-
"old\u2192new applied to disk content).",
|
|
101369
|
-
"",
|
|
101370
|
-
"Push-time equivalent: coderifts hook install (git exit 1 on BLOCK)."
|
|
101371
|
-
].join("\n");
|
|
101372
|
-
function isEnvFlag(env, name) {
|
|
101373
|
-
const v = env && env[name];
|
|
101374
|
-
if (v == null || v === "") return false;
|
|
101375
|
-
const s = String(v).trim().toLowerCase();
|
|
101376
|
-
return s === "1" || s === "true";
|
|
101377
|
-
}
|
|
101378
|
-
function isStrictMode(env = process.env) {
|
|
101379
|
-
return isEnvFlag(env, "CODERIFTS_STRICT");
|
|
101380
|
-
}
|
|
101381
|
-
function isAdvisoryMode(env = process.env) {
|
|
101382
|
-
return isEnvFlag(env, "CODERIFTS_ADVISORY");
|
|
101383
|
-
}
|
|
101384
|
-
function isMonitoringSinkWired({ env = process.env, deps = {}, result = null } = {}) {
|
|
101385
|
-
if (deps && deps.monitoringSinkWired === true) return true;
|
|
101386
|
-
if (isEnvFlag(env, "CODERIFTS_MONITORING_SINK_WIRED")) return true;
|
|
101387
|
-
const git = readGitConfig("coderifts.monitoringSinkWired", deps);
|
|
101388
|
-
if (git) {
|
|
101389
|
-
const s = String(git).trim().toLowerCase();
|
|
101390
|
-
if (s === "1" || s === "true") return true;
|
|
101391
|
-
}
|
|
101392
|
-
if (result && result.monitoringSinkWired === true) return true;
|
|
101393
|
-
const dr = result && result.decision_result;
|
|
101394
|
-
if (dr && typeof dr === "object" && dr.monitoringSinkWired === true) return true;
|
|
101395
|
-
return false;
|
|
101396
|
-
}
|
|
101397
|
-
var HOOK_TRIGGER_SOURCE = "claude_hook";
|
|
101398
|
-
function extractFingerprint(result) {
|
|
101399
|
-
const d = result && typeof result === "object" && !Array.isArray(result) ? result : {};
|
|
101400
|
-
const dr = d.decision_result && typeof d.decision_result === "object" ? d.decision_result : {};
|
|
101401
|
-
const fp = dr.fingerprint || d.fingerprint || d.verdict_fingerprint || d.input_fingerprint;
|
|
101402
|
-
return typeof fp === "string" && fp.length > 0 ? fp : void 0;
|
|
101403
|
-
}
|
|
101404
|
-
function fingerprintPrefix(fp) {
|
|
101405
|
-
if (typeof fp !== "string" || fp.length === 0) return void 0;
|
|
101406
|
-
const colon = fp.indexOf(":");
|
|
101407
|
-
if (colon > 0 && colon < fp.length - 1) {
|
|
101408
|
-
const scheme = fp.slice(0, colon + 1);
|
|
101409
|
-
const rest = fp.slice(colon + 1);
|
|
101410
|
-
return scheme + rest.slice(0, 12) + (rest.length > 12 ? "\u2026" : "");
|
|
101411
|
-
}
|
|
101412
|
-
return fp.length > 16 ? `${fp.slice(0, 16)}\u2026` : fp;
|
|
101413
|
-
}
|
|
101414
|
-
function renderAllowProofBlock({ decision, executionAction, decisionId, fingerprint } = {}) {
|
|
101415
|
-
const decPart = decision ? ` decision=${decision}` : "";
|
|
101416
|
-
const eaPart = executionAction ? ` execution_action=${executionAction}` : "";
|
|
101417
|
-
const idPart = decisionId ? ` decision_id=${decisionId}` : "";
|
|
101418
|
-
const lines = [`CodeRifts claude-hook: AUTHORIZED${decPart}${eaPart}${idPart}`];
|
|
101419
|
-
const pref = fingerprintPrefix(fingerprint);
|
|
101420
|
-
if (pref) lines.push(`- receipt: ${pref}`);
|
|
101421
|
-
lines.push("- Interpretation: authorized at verify time for the bound scope only \u2014 see Limits.");
|
|
101422
|
-
lines.push("- authorized; execution/commit not proven by this hook");
|
|
101423
|
-
return lines.join("\n");
|
|
101424
|
-
}
|
|
101425
|
-
function hookSessionId(env) {
|
|
101426
|
-
const fromEnv = env && env.CODERIFTS_SESSION_ID && String(env.CODERIFTS_SESSION_ID).trim();
|
|
101427
|
-
if (fromEnv) return fromEnv;
|
|
101428
|
-
if (!hookSessionId._gen) hookSessionId._gen = `hook-${process.pid}-${Date.now()}`;
|
|
101429
|
-
return hookSessionId._gen;
|
|
101430
|
-
}
|
|
101431
|
-
function appendGuardEventLog(env, event, deps = {}) {
|
|
101432
|
-
const p = env && env.CODERIFTS_GUARD_EVENT_LOG;
|
|
101433
|
-
if (p == null || String(p).trim() === "") return;
|
|
101434
|
-
const errLog = deps.errLog || ((m) => console.error(String(m)));
|
|
101435
|
-
const append = deps.appendFileSync || ((file, data) => fs.appendFileSync(file, data));
|
|
101436
|
-
try {
|
|
101437
|
-
append(String(p).trim(), JSON.stringify(event) + "\n");
|
|
101438
|
-
} catch (err) {
|
|
101439
|
-
try {
|
|
101440
|
-
errLog(`CodeRifts claude-hook: CODERIFTS_GUARD_EVENT_LOG write failed (${err && err.message})`);
|
|
101441
|
-
} catch {
|
|
101442
|
-
}
|
|
101443
|
-
}
|
|
101444
|
-
}
|
|
101445
|
-
function failClosedOrAdvisory({ advisory, strict, site, softMsg, why, errLog }) {
|
|
101446
|
-
if (advisory && !strict) {
|
|
101447
|
-
errLog(`${softMsg} (CODERIFTS_ADVISORY)`);
|
|
101448
|
-
return { exitCode: 0, reason: site, site };
|
|
101449
|
-
}
|
|
101450
|
-
if (strict) {
|
|
101451
|
-
errLog(
|
|
101452
|
-
`CodeRifts claude-hook: CODERIFTS_STRICT: governance could not run (${why}) \u2014 blocking. Set a key / restore network, or unset CODERIFTS_STRICT.`
|
|
101453
|
-
);
|
|
101454
|
-
return { exitCode: 2, reason: "enforce_indeterminate", site, strictBlocked: true };
|
|
101455
|
-
}
|
|
101456
|
-
errLog(
|
|
101457
|
-
`CodeRifts claude-hook: governance could not run (${why}) \u2014 blocking. Set CODERIFTS_ADVISORY=1 to allow without governance (explicit opt-out).`
|
|
101458
|
-
);
|
|
101459
|
-
return { exitCode: 2, reason: "enforce_indeterminate", site };
|
|
101460
|
-
}
|
|
101461
|
-
function softOrStrictBlock({ strict, reason, softMsg, why, errLog }) {
|
|
101462
|
-
return failClosedOrAdvisory({
|
|
101463
|
-
advisory: false,
|
|
101464
|
-
strict: !!strict,
|
|
101465
|
-
site: reason,
|
|
101466
|
-
softMsg,
|
|
101467
|
-
why,
|
|
101468
|
-
errLog
|
|
101469
|
-
});
|
|
101470
|
-
}
|
|
101471
|
-
function readGitConfig(key, deps = {}) {
|
|
101472
|
-
const run = deps.execSync || execSync;
|
|
101473
|
-
const cwd = deps.cwd || process.cwd();
|
|
101474
|
-
try {
|
|
101475
|
-
const v = run(`git config ${key}`, { encoding: "utf8", cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
101476
|
-
const t = String(v || "").trim();
|
|
101477
|
-
return t || null;
|
|
101478
|
-
} catch {
|
|
101479
|
-
return null;
|
|
101480
|
-
}
|
|
101481
|
-
}
|
|
101482
|
-
function resolveApiKey(deps = {}) {
|
|
101483
|
-
const getKey = deps.getApiKey || getApiKey;
|
|
101484
|
-
const fromLogin = getKey();
|
|
101485
|
-
if (fromLogin) return fromLogin;
|
|
101486
|
-
const env = deps.env || process.env;
|
|
101487
|
-
const fromEnv = env.CODERIFTS_API_KEY && String(env.CODERIFTS_API_KEY).trim();
|
|
101488
|
-
if (fromEnv) return fromEnv;
|
|
101489
|
-
return readGitConfig("coderifts.apiKey", deps);
|
|
101490
|
-
}
|
|
101491
|
-
function resolveSpecPath(deps = {}) {
|
|
101492
|
-
const fromGit = readGitConfig("coderifts.specPath", deps);
|
|
101493
|
-
if (fromGit) return fromGit;
|
|
101494
|
-
return DEFAULT_SPEC_PATH;
|
|
101495
|
-
}
|
|
101496
|
-
function parseStdinJson(raw) {
|
|
101497
|
-
if (raw == null || String(raw).trim() === "") {
|
|
101498
|
-
return { ok: false, reason: "empty stdin" };
|
|
101499
|
-
}
|
|
101500
|
-
let obj;
|
|
101501
|
-
try {
|
|
101502
|
-
obj = JSON.parse(String(raw));
|
|
101503
|
-
} catch {
|
|
101504
|
-
return { ok: false, reason: "stdin is not JSON" };
|
|
101505
|
-
}
|
|
101506
|
-
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
|
|
101507
|
-
return { ok: false, reason: "stdin JSON is not an object" };
|
|
101508
|
-
}
|
|
101509
|
-
const toolName = obj.tool_name || obj.toolName || obj.name || null;
|
|
101510
|
-
let toolInput = obj.tool_input || obj.toolInput || obj.input || null;
|
|
101511
|
-
if (toolInput == null && obj.file_path) toolInput = obj;
|
|
101512
|
-
if (!toolName || typeof toolName !== "string") {
|
|
101513
|
-
return { ok: false, reason: "missing tool_name" };
|
|
101514
|
-
}
|
|
101515
|
-
if (!toolInput || typeof toolInput !== "object") {
|
|
101516
|
-
return { ok: false, reason: "missing tool_input" };
|
|
101517
|
-
}
|
|
101518
|
-
return { ok: true, toolName: String(toolName), toolInput };
|
|
101519
|
-
}
|
|
101520
|
-
function isSpecPath(filePath, specPath) {
|
|
101521
|
-
if (!filePath || !specPath) return false;
|
|
101522
|
-
const fp = path.normalize(String(filePath).replace(/\\/g, "/"));
|
|
101523
|
-
const sp = path.normalize(String(specPath).replace(/\\/g, "/"));
|
|
101524
|
-
if (fp === sp) return true;
|
|
101525
|
-
if (fp.endsWith("/" + sp) || fp.endsWith(sp)) return true;
|
|
101526
|
-
const baseFp = path.basename(fp);
|
|
101527
|
-
const baseSp = path.basename(sp);
|
|
101528
|
-
if (baseFp === baseSp && (fp.endsWith(sp) || sp.endsWith(baseSp))) {
|
|
101529
|
-
const tail = sp.split("/").filter(Boolean).join("/");
|
|
101530
|
-
return fp.replace(/\\/g, "/").endsWith(tail);
|
|
101531
|
-
}
|
|
101532
|
-
return false;
|
|
101533
|
-
}
|
|
101534
|
-
function deriveAfterContent(toolName, toolInput, diskBefore) {
|
|
101535
|
-
const name = String(toolName || "");
|
|
101536
|
-
if (name === "Write" || name === "write") {
|
|
101537
|
-
if (typeof toolInput.content !== "string") {
|
|
101538
|
-
return { ok: false, reason: "Write tool_input.content missing or not a string" };
|
|
101539
|
-
}
|
|
101540
|
-
return { ok: true, after: toolInput.content };
|
|
101541
|
-
}
|
|
101542
|
-
if (name === "Edit" || name === "edit") {
|
|
101543
|
-
const oldS = toolInput.old_string != null ? toolInput.old_string : toolInput.oldString;
|
|
101544
|
-
const newS = toolInput.new_string != null ? toolInput.new_string : toolInput.newString;
|
|
101545
|
-
if (typeof oldS !== "string" || typeof newS !== "string") {
|
|
101546
|
-
return { ok: false, reason: "Edit tool_input.old_string/new_string missing" };
|
|
101547
|
-
}
|
|
101548
|
-
if (!diskBefore.includes(oldS)) {
|
|
101549
|
-
return { ok: false, reason: "Edit old_string not found in disk content" };
|
|
101550
|
-
}
|
|
101551
|
-
return { ok: true, after: diskBefore.replace(oldS, newS) };
|
|
101552
|
-
}
|
|
101553
|
-
if (name === "MultiEdit" || name === "multi_edit" || name === "multiEdit") {
|
|
101554
|
-
const edits = toolInput.edits || toolInput.Edits;
|
|
101555
|
-
if (!Array.isArray(edits) || edits.length === 0) {
|
|
101556
|
-
return { ok: false, reason: "MultiEdit tool_input.edits missing or empty" };
|
|
101557
|
-
}
|
|
101558
|
-
let cur = diskBefore;
|
|
101559
|
-
for (let i = 0; i < edits.length; i++) {
|
|
101560
|
-
const e = edits[i] || {};
|
|
101561
|
-
const oldS = e.old_string != null ? e.old_string : e.oldString;
|
|
101562
|
-
const newS = e.new_string != null ? e.new_string : e.newString;
|
|
101563
|
-
if (typeof oldS !== "string" || typeof newS !== "string") {
|
|
101564
|
-
return { ok: false, reason: `MultiEdit edits[${i}] missing old_string/new_string` };
|
|
101565
|
-
}
|
|
101566
|
-
if (!cur.includes(oldS)) {
|
|
101567
|
-
return { ok: false, reason: `MultiEdit edits[${i}] old_string not found in content` };
|
|
101568
|
-
}
|
|
101569
|
-
cur = cur.replace(oldS, newS);
|
|
101570
|
-
}
|
|
101571
|
-
return { ok: true, after: cur };
|
|
101572
|
-
}
|
|
101573
|
-
return { ok: false, reason: `unsupported tool_name for content derive: ${name}` };
|
|
101574
|
-
}
|
|
101575
|
-
function isV2DecisionBody(d, dr) {
|
|
101576
|
-
if (dr && typeof dr === "object") return true;
|
|
101577
|
-
if (d.preflight_mode != null && d.preflight_mode !== "") return true;
|
|
101578
|
-
const ver = d.decision_spec_version;
|
|
101579
|
-
return typeof ver === "string" && ver.startsWith("2.");
|
|
101839
|
+
lines.push(chalk.dim("High fire_count \u2192 candidate false-positive noise (not safe to default-block without review)."));
|
|
101840
|
+
lines.push(chalk.dim("Rare + CRITICAL/HIGH \u2192 safer default-block candidates. Human decides; no auto-flip."));
|
|
101841
|
+
lines.push("");
|
|
101842
|
+
return lines.join("\n");
|
|
101580
101843
|
}
|
|
101581
|
-
function
|
|
101582
|
-
|
|
101844
|
+
function pad(s, n) {
|
|
101845
|
+
const t = String(s || "");
|
|
101846
|
+
if (t.length >= n) return `${t.slice(0, n - 1)} `;
|
|
101847
|
+
return t + " ".repeat(n - t.length);
|
|
101583
101848
|
}
|
|
101584
|
-
function
|
|
101585
|
-
const
|
|
101586
|
-
|
|
101587
|
-
const
|
|
101588
|
-
|
|
101589
|
-
|
|
101590
|
-
|
|
101591
|
-
|
|
101849
|
+
async function runAdopt(options = {}, deps = {}) {
|
|
101850
|
+
const log = deps.log || console.log.bind(console);
|
|
101851
|
+
const logErr = deps.logErr || console.error.bind(console);
|
|
101852
|
+
const load = deps.loadCore || loadCore;
|
|
101853
|
+
const collected = collectChangeSetsFromGit(options, deps);
|
|
101854
|
+
if (!collected.ok) {
|
|
101855
|
+
logErr(chalk.red(`CodeRifts adopt: ${collected.code} \u2014 ${collected.message}`));
|
|
101856
|
+
return { ok: false, exitCode: 1, code: collected.code, message: collected.message };
|
|
101592
101857
|
}
|
|
101593
|
-
|
|
101594
|
-
|
|
101595
|
-
|
|
101596
|
-
|
|
101597
|
-
|
|
101598
|
-
|
|
101599
|
-
executionAction: null
|
|
101600
|
-
};
|
|
101858
|
+
let core;
|
|
101859
|
+
try {
|
|
101860
|
+
core = load();
|
|
101861
|
+
} catch (err) {
|
|
101862
|
+
logErr(chalk.red(`CodeRifts adopt: ${err.message}`));
|
|
101863
|
+
return { ok: false, exitCode: 1, code: "CORE_LOAD_ERROR", message: err.message };
|
|
101601
101864
|
}
|
|
101602
|
-
|
|
101603
|
-
|
|
101604
|
-
|
|
101605
|
-
|
|
101606
|
-
|
|
101607
|
-
|
|
101608
|
-
|
|
101609
|
-
|
|
101610
|
-
|
|
101611
|
-
|
|
101612
|
-
} else {
|
|
101613
|
-
severity = "REQUIRE_APPROVAL";
|
|
101614
|
-
}
|
|
101615
|
-
} else if (allowLegacyDecisionMap(d, dr)) {
|
|
101616
|
-
const od = d.omega_decision || d.decision;
|
|
101617
|
-
if (od == null || od === "") {
|
|
101618
|
-
severity = "INDETERMINATE";
|
|
101619
|
-
} else if (od === "BLOCK") severity = "BLOCK";
|
|
101620
|
-
else if (od === "REQUIRE_APPROVAL") severity = "REQUIRE_APPROVAL";
|
|
101621
|
-
else if (od === "WARN") severity = "WARN";
|
|
101622
|
-
else severity = "ALLOW";
|
|
101865
|
+
const runCore = () => core.buildCounterfactualReport(collected.changeSets, deps.config || {});
|
|
101866
|
+
const report = options.json ? await withJsonQuietAnalyzerLogs(runCore) : await runCore();
|
|
101867
|
+
if (options.json) {
|
|
101868
|
+
log(JSON.stringify({
|
|
101869
|
+
...report,
|
|
101870
|
+
meta: {
|
|
101871
|
+
commit_count: collected.commit_count,
|
|
101872
|
+
change_set_count: collected.changeSets.length
|
|
101873
|
+
}
|
|
101874
|
+
}, null, 2));
|
|
101623
101875
|
} else {
|
|
101624
|
-
|
|
101876
|
+
log(renderHuman(report, { commit_count: collected.commit_count }));
|
|
101625
101877
|
}
|
|
101626
|
-
const decision = dr && dr.decision || d.decision || d.omega_decision || null;
|
|
101627
|
-
const decisionId = dr && dr.decision_id || d.decision_id || null;
|
|
101628
101878
|
return {
|
|
101629
|
-
|
|
101630
|
-
|
|
101631
|
-
|
|
101632
|
-
|
|
101879
|
+
ok: true,
|
|
101880
|
+
exitCode: 0,
|
|
101881
|
+
report,
|
|
101882
|
+
meta: { commit_count: collected.commit_count }
|
|
101633
101883
|
};
|
|
101634
101884
|
}
|
|
101635
|
-
|
|
101636
|
-
|
|
101637
|
-
|
|
101638
|
-
|
|
101639
|
-
|
|
101640
|
-
|
|
101641
|
-
|
|
101642
|
-
|
|
101643
|
-
|
|
101644
|
-
|
|
101645
|
-
|
|
101646
|
-
|
|
101647
|
-
|
|
101648
|
-
|
|
101649
|
-
|
|
101650
|
-
|
|
101651
|
-
|
|
101652
|
-
|
|
101653
|
-
|
|
101654
|
-
|
|
101655
|
-
|
|
101656
|
-
|
|
101657
|
-
|
|
101658
|
-
|
|
101659
|
-
|
|
101660
|
-
|
|
101661
|
-
|
|
101662
|
-
|
|
101663
|
-
|
|
101664
|
-
|
|
101665
|
-
|
|
101666
|
-
|
|
101667
|
-
|
|
101668
|
-
|
|
101669
|
-
|
|
101670
|
-
|
|
101671
|
-
|
|
101672
|
-
|
|
101885
|
+
module2.exports = {
|
|
101886
|
+
runAdopt,
|
|
101887
|
+
collectChangeSetsFromGit,
|
|
101888
|
+
isContractPath,
|
|
101889
|
+
withJsonQuietAnalyzerLogs,
|
|
101890
|
+
renderHuman,
|
|
101891
|
+
defaultGit,
|
|
101892
|
+
SOURCE_CODE_EXT_RE
|
|
101893
|
+
};
|
|
101894
|
+
}
|
|
101895
|
+
});
|
|
101896
|
+
|
|
101897
|
+
// src/commands/outcome.js
|
|
101898
|
+
var require_outcome = __commonJS({
|
|
101899
|
+
"src/commands/outcome.js"(exports2, module2) {
|
|
101900
|
+
"use strict";
|
|
101901
|
+
var chalk = require_source();
|
|
101902
|
+
var { getApiKey } = require_config();
|
|
101903
|
+
var { cloudPostOutcome } = require_cloud();
|
|
101904
|
+
if (process.env.NO_COLOR) chalk.level = 0;
|
|
101905
|
+
var OUTCOME_KINDS = Object.freeze([
|
|
101906
|
+
"deploy_succeeded",
|
|
101907
|
+
"deploy_failed",
|
|
101908
|
+
"rolled_back",
|
|
101909
|
+
"consumer_break_reported",
|
|
101910
|
+
"remediation_verified_working",
|
|
101911
|
+
"false_positive_reported",
|
|
101912
|
+
"other_reported"
|
|
101913
|
+
]);
|
|
101914
|
+
var OUTCOME_KIND_SET = new Set(OUTCOME_KINDS);
|
|
101915
|
+
var USAGE = [
|
|
101916
|
+
"Usage: coderifts outcome <kind> --decision <decision_id> [--observed-at <ISO>] [--details <json>] [--json]",
|
|
101917
|
+
"",
|
|
101918
|
+
"Report a post-hoc observed outcome for a past decision_id to the CodeRifts cloud API.",
|
|
101919
|
+
"This records the CALLER'S assertion (e.g. your deploy job knows success/failure) \u2014",
|
|
101920
|
+
"the command does NOT itself verify that a deploy succeeded or failed.",
|
|
101921
|
+
"",
|
|
101922
|
+
"kind (required, closed set):",
|
|
101923
|
+
` ${OUTCOME_KINDS.join(", ")}`,
|
|
101924
|
+
"",
|
|
101925
|
+
"Requires a cloud API key (coderifts login or CODERIFTS_API_KEY).",
|
|
101926
|
+
"POST /api/v1/outcomes \u2014 source is always reported (server-side); reporter is derived from the key."
|
|
101927
|
+
].join("\n");
|
|
101928
|
+
function isValidOutcomeKind(kind) {
|
|
101929
|
+
return typeof kind === "string" && OUTCOME_KIND_SET.has(kind);
|
|
101673
101930
|
}
|
|
101674
|
-
|
|
101675
|
-
|
|
101676
|
-
const
|
|
101677
|
-
|
|
101678
|
-
|
|
101679
|
-
|
|
101680
|
-
|
|
101681
|
-
|
|
101682
|
-
|
|
101683
|
-
|
|
101684
|
-
|
|
101685
|
-
|
|
101686
|
-
const cwd = deps.cwd || process.cwd();
|
|
101687
|
-
const env = deps.env || process.env;
|
|
101688
|
-
const strict = isStrictMode(env);
|
|
101689
|
-
const advisory = isAdvisoryMode(env);
|
|
101690
|
-
const sessionId = hookSessionId(env);
|
|
101691
|
-
const logEv = (partial) => {
|
|
101692
|
-
appendGuardEventLog(env, {
|
|
101693
|
-
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
101694
|
-
sessionId,
|
|
101695
|
-
trigger_source: HOOK_TRIGGER_SOURCE,
|
|
101696
|
-
...partial
|
|
101697
|
-
}, { errLog, appendFileSync: deps.appendFileSync });
|
|
101698
|
-
};
|
|
101699
|
-
let tool = null;
|
|
101700
|
-
let filePathKnown = null;
|
|
101701
|
-
const emitTerminal = (r) => {
|
|
101702
|
-
const extra = {
|
|
101703
|
-
tool: tool || void 0,
|
|
101704
|
-
path: filePathKnown || void 0,
|
|
101705
|
-
decision: r.decision != null ? r.decision : void 0,
|
|
101706
|
-
decisionId: r.decisionId != null ? r.decisionId : void 0
|
|
101707
|
-
};
|
|
101708
|
-
if (r.exitCode === 2) {
|
|
101709
|
-
const reasons = Array.isArray(r.reasons) && r.reasons.length ? r.reasons : void 0;
|
|
101710
|
-
logEv({
|
|
101711
|
-
type: "hook_blocked",
|
|
101712
|
-
exit: 2,
|
|
101713
|
-
...extra,
|
|
101714
|
-
reasons,
|
|
101715
|
-
cause: r.reason || r.site
|
|
101716
|
-
});
|
|
101717
|
-
} else if (r.exitCode === 0 && r.site && advisory && !strict) {
|
|
101718
|
-
logEv({ type: "hook_advisory_passthrough", exit: 0, decision: r.decision || null, ...extra, cause: r.site });
|
|
101719
|
-
} else {
|
|
101720
|
-
const proof = r.reason === "allow" ? {
|
|
101721
|
-
decision_id: r.decisionId != null ? r.decisionId : extra.decisionId || null,
|
|
101722
|
-
fp_prefix: fingerprintPrefix(r.fingerprint) || null
|
|
101723
|
-
} : void 0;
|
|
101724
|
-
logEv({
|
|
101725
|
-
type: "hook_continue",
|
|
101726
|
-
exit: 0,
|
|
101727
|
-
...extra,
|
|
101728
|
-
cause: r.reason,
|
|
101729
|
-
...proof ? { proof } : {}
|
|
101730
|
-
});
|
|
101931
|
+
function isValidObservedAt(iso) {
|
|
101932
|
+
if (typeof iso !== "string" || !iso.trim()) return false;
|
|
101933
|
+
const t = Date.parse(iso);
|
|
101934
|
+
return Number.isFinite(t);
|
|
101935
|
+
}
|
|
101936
|
+
function parseDetails(raw) {
|
|
101937
|
+
if (raw == null || raw === "") return { ok: true, details: null };
|
|
101938
|
+
if (typeof raw !== "string") return { ok: false, error: "--details must be a JSON string" };
|
|
101939
|
+
try {
|
|
101940
|
+
const v = JSON.parse(raw);
|
|
101941
|
+
if (v !== null && typeof v !== "object") {
|
|
101942
|
+
return { ok: false, error: "--details must be a JSON object or array" };
|
|
101731
101943
|
}
|
|
101732
|
-
return
|
|
101733
|
-
}
|
|
101734
|
-
|
|
101735
|
-
if (!apiKey) {
|
|
101736
|
-
logEv({ type: "preflight_unavailable", cause: "missing_api_key" });
|
|
101737
|
-
return emitTerminal(failClosedOrAdvisory({
|
|
101738
|
-
advisory,
|
|
101739
|
-
strict,
|
|
101740
|
-
site: "missing_api_key",
|
|
101741
|
-
why: "no API key",
|
|
101742
|
-
softMsg: "CodeRifts claude-hook: no API key (coderifts login / CODERIFTS_API_KEY / git config coderifts.apiKey) \u2014 allowing",
|
|
101743
|
-
errLog
|
|
101744
|
-
}));
|
|
101944
|
+
return { ok: true, details: v };
|
|
101945
|
+
} catch (e) {
|
|
101946
|
+
return { ok: false, error: `--details is not valid JSON: ${e && e.message || "parse error"}` };
|
|
101745
101947
|
}
|
|
101746
|
-
|
|
101747
|
-
|
|
101748
|
-
const
|
|
101749
|
-
|
|
101750
|
-
|
|
101751
|
-
|
|
101752
|
-
|
|
101753
|
-
|
|
101754
|
-
errLog(
|
|
101755
|
-
|
|
101948
|
+
}
|
|
101949
|
+
async function runOutcome(kind, options = {}, deps = {}) {
|
|
101950
|
+
const getKey = deps.getApiKey || getApiKey;
|
|
101951
|
+
const postOutcome = deps.cloudPostOutcome || cloudPostOutcome;
|
|
101952
|
+
const log = deps.log || console.log;
|
|
101953
|
+
const errLog = deps.errLog || console.error;
|
|
101954
|
+
const nowIso = deps.nowIso || (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
101955
|
+
if (kind == null || String(kind).trim() === "") {
|
|
101956
|
+
errLog(chalk.red("Error: missing outcome kind"));
|
|
101957
|
+
errLog(USAGE);
|
|
101958
|
+
return { exitCode: 1, error: "missing_kind" };
|
|
101756
101959
|
}
|
|
101757
|
-
const
|
|
101758
|
-
|
|
101759
|
-
|
|
101760
|
-
|
|
101761
|
-
|
|
101762
|
-
errLog("CodeRifts claude-hook: tool_input.file_path missing \u2014 allowing (CODERIFTS_ADVISORY)");
|
|
101763
|
-
return emitTerminal({ exitCode: 0, reason: "missing_file_path", site: "missing_file_path" });
|
|
101764
|
-
}
|
|
101765
|
-
errLog(`CodeRifts claude-hook: ${PARSE_GAP_STDERR}`);
|
|
101766
|
-
return emitTerminal({ exitCode: 2, reason: "missing_file_path" });
|
|
101960
|
+
const outcomeKind = String(kind).trim();
|
|
101961
|
+
if (!isValidOutcomeKind(outcomeKind)) {
|
|
101962
|
+
errLog(chalk.red(`Error: invalid outcome kind '${outcomeKind}'`));
|
|
101963
|
+
errLog(chalk.dim(` Valid kinds: ${OUTCOME_KINDS.join(", ")}`));
|
|
101964
|
+
return { exitCode: 1, error: "invalid_kind" };
|
|
101767
101965
|
}
|
|
101768
|
-
|
|
101769
|
-
|
|
101770
|
-
|
|
101771
|
-
|
|
101772
|
-
return
|
|
101966
|
+
const decisionId = options.decision != null ? String(options.decision).trim() : "";
|
|
101967
|
+
if (!decisionId) {
|
|
101968
|
+
errLog(chalk.red("Error: --decision <decision_id> is required"));
|
|
101969
|
+
errLog(USAGE);
|
|
101970
|
+
return { exitCode: 1, error: "missing_decision" };
|
|
101773
101971
|
}
|
|
101774
|
-
|
|
101775
|
-
|
|
101776
|
-
|
|
101777
|
-
|
|
101778
|
-
|
|
101779
|
-
|
|
101780
|
-
logEv({ type: "preflight_unavailable", cause: "disk_unreadable", tool, path: filePath });
|
|
101781
|
-
return emitTerminal(failClosedOrAdvisory({
|
|
101782
|
-
advisory,
|
|
101783
|
-
strict,
|
|
101784
|
-
site: "disk_unreadable",
|
|
101785
|
-
why: `cannot read contract file ${absPath}`,
|
|
101786
|
-
softMsg: `CodeRifts claude-hook: cannot read ${absPath} \u2014 allowing (soft)`,
|
|
101787
|
-
errLog
|
|
101788
|
-
}));
|
|
101972
|
+
let observedAt;
|
|
101973
|
+
if (options.observedAt != null && String(options.observedAt).trim() !== "") {
|
|
101974
|
+
observedAt = String(options.observedAt).trim();
|
|
101975
|
+
if (!isValidObservedAt(observedAt)) {
|
|
101976
|
+
errLog(chalk.red("Error: --observed-at must be a valid ISO-8601 timestamp"));
|
|
101977
|
+
return { exitCode: 1, error: "invalid_observed_at" };
|
|
101789
101978
|
}
|
|
101979
|
+
} else {
|
|
101980
|
+
observedAt = nowIso();
|
|
101790
101981
|
}
|
|
101791
|
-
const
|
|
101792
|
-
if (!
|
|
101793
|
-
|
|
101794
|
-
return
|
|
101795
|
-
advisory,
|
|
101796
|
-
strict,
|
|
101797
|
-
site: "edit_apply_failed",
|
|
101798
|
-
why: derived.reason,
|
|
101799
|
-
softMsg: `CodeRifts claude-hook: ${derived.reason} \u2014 allowing (soft; never guess edit apply)`,
|
|
101800
|
-
errLog
|
|
101801
|
-
}));
|
|
101982
|
+
const det = parseDetails(options.details);
|
|
101983
|
+
if (!det.ok) {
|
|
101984
|
+
errLog(chalk.red(`Error: ${det.error}`));
|
|
101985
|
+
return { exitCode: 1, error: "invalid_details" };
|
|
101802
101986
|
}
|
|
101803
|
-
|
|
101804
|
-
|
|
101805
|
-
|
|
101987
|
+
const apiKey = getKey();
|
|
101988
|
+
if (!apiKey) {
|
|
101989
|
+
errLog(chalk.red("Error: no API key. Run `coderifts login` or set CODERIFTS_API_KEY."));
|
|
101990
|
+
return { exitCode: 1, error: "missing_api_key" };
|
|
101806
101991
|
}
|
|
101807
|
-
|
|
101808
|
-
|
|
101992
|
+
const body = {
|
|
101993
|
+
decision_id: decisionId,
|
|
101994
|
+
outcome_kind: outcomeKind,
|
|
101995
|
+
observed_at: observedAt
|
|
101996
|
+
};
|
|
101997
|
+
if (det.details != null) body.details = det.details;
|
|
101998
|
+
let response;
|
|
101809
101999
|
try {
|
|
101810
|
-
|
|
101811
|
-
operation: "merge",
|
|
101812
|
-
environment: "staging"
|
|
101813
|
-
});
|
|
102000
|
+
response = await postOutcome(apiKey, body);
|
|
101814
102001
|
} catch (e) {
|
|
101815
102002
|
const msg = e && e.message ? String(e.message) : "request failed";
|
|
101816
|
-
|
|
101817
|
-
|
|
101818
|
-
|
|
101819
|
-
|
|
101820
|
-
site: "api_unreachable",
|
|
101821
|
-
why: `API unreachable: ${msg}`,
|
|
101822
|
-
softMsg: `CodeRifts claude-hook: API unreachable (${msg}) \u2014 allowing (soft; availability must not brick the editor)`,
|
|
101823
|
-
errLog
|
|
101824
|
-
}));
|
|
101825
|
-
}
|
|
101826
|
-
const mapped = mapDecisionSeverity(result);
|
|
101827
|
-
const fingerprint = extractFingerprint(result);
|
|
101828
|
-
const idPart = mapped.decisionId ? ` decision_id=${mapped.decisionId}` : "";
|
|
101829
|
-
const decPart = mapped.decision ? ` decision=${mapped.decision}` : "";
|
|
101830
|
-
const eaPart = mapped.executionAction ? ` execution_action=${mapped.executionAction}` : "";
|
|
101831
|
-
const actionForLog = mapped.executionAction || (mapped.severity === "BLOCK" || mapped.severity === "UNKNOWN" || mapped.severity === "INDETERMINATE" ? "STOP" : mapped.severity === "REQUIRE_APPROVAL" ? "REQUEST_APPROVAL" : mapped.severity === "MONITOR" || mapped.severity === "WARN" ? "CONTINUE_WITH_MONITORING" : "CONTINUE");
|
|
101832
|
-
logEv({
|
|
101833
|
-
type: "preflight_result",
|
|
101834
|
-
action: actionForLog,
|
|
101835
|
-
decisionId: mapped.decisionId || void 0,
|
|
101836
|
-
fingerprint,
|
|
101837
|
-
tool,
|
|
101838
|
-
path: filePath
|
|
101839
|
-
});
|
|
101840
|
-
if (mapped.severity === "INDETERMINATE") {
|
|
101841
|
-
errLog(
|
|
101842
|
-
"CodeRifts claude-hook: BLOCKED (indeterminate response \u2014 no execution_action and no decision; not permission)"
|
|
101843
|
-
);
|
|
101844
|
-
return emitTerminal({
|
|
101845
|
-
exitCode: 2,
|
|
101846
|
-
reason: "indeterminate",
|
|
101847
|
-
severity: "INDETERMINATE",
|
|
101848
|
-
decision: mapped.decision,
|
|
101849
|
-
decisionId: mapped.decisionId
|
|
101850
|
-
});
|
|
101851
|
-
}
|
|
101852
|
-
if (mapped.severity === "BLOCK" || mapped.severity === "UNKNOWN") {
|
|
101853
|
-
const why = renderDecisionWhy(result);
|
|
101854
|
-
errLog(
|
|
101855
|
-
[
|
|
101856
|
-
`CodeRifts claude-hook: BLOCKED${decPart}${eaPart}${idPart}` + (mapped.severity === "UNKNOWN" ? " (unrecognised execution_action)" : ""),
|
|
101857
|
-
...why.lines
|
|
101858
|
-
].join("\n")
|
|
101859
|
-
);
|
|
101860
|
-
return emitTerminal({
|
|
101861
|
-
exitCode: 2,
|
|
101862
|
-
reason: mapped.severity === "UNKNOWN" ? "unknown_action" : "block",
|
|
101863
|
-
severity: mapped.severity,
|
|
101864
|
-
decision: mapped.decision,
|
|
101865
|
-
decisionId: mapped.decisionId,
|
|
101866
|
-
reasons: why.reasonCodes
|
|
101867
|
-
});
|
|
101868
|
-
}
|
|
101869
|
-
if (mapped.severity === "REQUIRE_APPROVAL") {
|
|
101870
|
-
const why = renderDecisionWhy(result);
|
|
101871
|
-
errLog(
|
|
101872
|
-
[
|
|
101873
|
-
`CodeRifts claude-hook: BLOCKED approval_required${decPart}${eaPart}${idPart}`,
|
|
101874
|
-
...why.lines
|
|
101875
|
-
].join("\n")
|
|
101876
|
-
);
|
|
101877
|
-
return emitTerminal({
|
|
101878
|
-
exitCode: 2,
|
|
101879
|
-
reason: "approval_required",
|
|
101880
|
-
severity: mapped.severity,
|
|
101881
|
-
decision: mapped.decision,
|
|
101882
|
-
decisionId: mapped.decisionId,
|
|
101883
|
-
reasons: why.reasonCodes
|
|
101884
|
-
});
|
|
102003
|
+
errLog(chalk.red(`Error: ${msg}`));
|
|
102004
|
+
if (e && e.statusCode) errLog(chalk.dim(` (HTTP ${e.statusCode})`));
|
|
102005
|
+
if (e && e.code) errLog(chalk.dim(` (${e.code})`));
|
|
102006
|
+
return { exitCode: 1, error: msg, body };
|
|
101885
102007
|
}
|
|
101886
|
-
if (
|
|
101887
|
-
|
|
101888
|
-
|
|
101889
|
-
|
|
101890
|
-
|
|
101891
|
-
|
|
101892
|
-
|
|
101893
|
-
|
|
101894
|
-
|
|
101895
|
-
|
|
101896
|
-
|
|
101897
|
-
decisionId: mapped.decisionId
|
|
101898
|
-
});
|
|
101899
|
-
}
|
|
101900
|
-
errLog(renderAllowProofBlock({
|
|
101901
|
-
decision: mapped.decision,
|
|
101902
|
-
executionAction: mapped.executionAction,
|
|
101903
|
-
decisionId: mapped.decisionId,
|
|
101904
|
-
fingerprint
|
|
101905
|
-
}));
|
|
101906
|
-
return emitTerminal({
|
|
101907
|
-
exitCode: 0,
|
|
101908
|
-
reason: "allow",
|
|
101909
|
-
severity: mapped.severity === "WARN" ? "MONITOR" : mapped.severity,
|
|
101910
|
-
decision: mapped.decision,
|
|
101911
|
-
decisionId: mapped.decisionId,
|
|
101912
|
-
fingerprint
|
|
101913
|
-
});
|
|
102008
|
+
if (options.json) {
|
|
102009
|
+
log(JSON.stringify(response, null, 2));
|
|
102010
|
+
} else {
|
|
102011
|
+
const o = response && response.outcome ? response.outcome : response;
|
|
102012
|
+
log(chalk.bold("CodeRifts outcome recorded"));
|
|
102013
|
+
log(` kind: ${outcomeKind}`);
|
|
102014
|
+
log(` decision_id: ${decisionId}`);
|
|
102015
|
+
log(` observed_at: ${observedAt}`);
|
|
102016
|
+
if (o && o.id) log(` id: ${o.id}`);
|
|
102017
|
+
if (response && response.source) log(` source: ${response.source}`);
|
|
102018
|
+
log(chalk.dim(" Caller assertion only \u2014 this command did not verify the deploy."));
|
|
101914
102019
|
}
|
|
101915
|
-
|
|
101916
|
-
decision: mapped.decision,
|
|
101917
|
-
executionAction: mapped.executionAction,
|
|
101918
|
-
decisionId: mapped.decisionId,
|
|
101919
|
-
fingerprint
|
|
101920
|
-
}));
|
|
101921
|
-
return emitTerminal({
|
|
101922
|
-
exitCode: 0,
|
|
101923
|
-
reason: "allow",
|
|
101924
|
-
severity: "ALLOW",
|
|
101925
|
-
decision: mapped.decision,
|
|
101926
|
-
decisionId: mapped.decisionId,
|
|
101927
|
-
fingerprint
|
|
101928
|
-
});
|
|
102020
|
+
return { exitCode: 0, response, body };
|
|
101929
102021
|
}
|
|
101930
102022
|
module2.exports = {
|
|
101931
|
-
|
|
101932
|
-
|
|
101933
|
-
|
|
101934
|
-
|
|
101935
|
-
|
|
101936
|
-
|
|
101937
|
-
resolveApiKey,
|
|
101938
|
-
resolveSpecPath,
|
|
101939
|
-
readGitConfig,
|
|
101940
|
-
isEnvFlag,
|
|
101941
|
-
isStrictMode,
|
|
101942
|
-
isAdvisoryMode,
|
|
101943
|
-
isMonitoringSinkWired,
|
|
101944
|
-
failClosedOrAdvisory,
|
|
101945
|
-
softOrStrictBlock,
|
|
101946
|
-
DEFAULT_SPEC_PATH,
|
|
101947
|
-
CLOSED_ACTIONS,
|
|
101948
|
-
USAGE,
|
|
101949
|
-
appendGuardEventLog,
|
|
101950
|
-
extractFingerprint,
|
|
101951
|
-
fingerprintPrefix,
|
|
101952
|
-
renderAllowProofBlock,
|
|
101953
|
-
HOOK_TRIGGER_SOURCE
|
|
102023
|
+
runOutcome,
|
|
102024
|
+
isValidOutcomeKind,
|
|
102025
|
+
isValidObservedAt,
|
|
102026
|
+
parseDetails,
|
|
102027
|
+
OUTCOME_KINDS,
|
|
102028
|
+
USAGE
|
|
101954
102029
|
};
|
|
101955
102030
|
}
|
|
101956
102031
|
});
|
|
@@ -103843,7 +103918,7 @@ program.command("diff <old-spec> <new-spec>").description("Compare two OpenAPI s
|
|
|
103843
103918
|
const { diff } = require_diff();
|
|
103844
103919
|
await diff(oldSpec, newSpec, options);
|
|
103845
103920
|
});
|
|
103846
|
-
program.command("deploy-gate").description("Gate a deploy on the current { environment, artifact } using a preflight receipt (
|
|
103921
|
+
program.command("deploy-gate").description("Gate a deploy on the current { environment, artifact } using a preflight receipt (fail-closed by default; CODERIFTS_DEPLOY_ADVISORY=1 to soften)").option("--env <environment>", "Target environment (e.g. production, staging)").option("--artifact <artifact_id>", "Immutable artifact identity being deployed (content digest or commit SHA)").option("--receipt <file>", "Path to the deploy-scoped receipt JSON produced by preflight").option("--json", "Output the binding result as JSON").option("--enforce", "Accepted silently (exit is fail-closed by default). Still attests ENFORCING for inescapable_deploy.").action(async (options) => {
|
|
103847
103922
|
const { runDeployGate } = require_deploy_gate2();
|
|
103848
103923
|
await runDeployGate(options);
|
|
103849
103924
|
});
|