coderifts 8.6.2 → 8.6.4
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 +10 -1
- package/README.md +6 -3
- package/dist/.build-source-sha +3 -3
- package/dist/cli.js +816 -130
- package/package.json +7 -8
- package/scripts/build.js +8 -24
- package/scripts/check-packed-install.js +62 -1
- package/scripts/lib/source-fingerprint.js +56 -0
package/dist/cli.js
CHANGED
|
@@ -3028,7 +3028,7 @@ var require_package = __commonJS({
|
|
|
3028
3028
|
"package.json"(exports2, module2) {
|
|
3029
3029
|
module2.exports = {
|
|
3030
3030
|
name: "coderifts",
|
|
3031
|
-
version: "8.6.
|
|
3031
|
+
version: "8.6.4",
|
|
3032
3032
|
description: "Detect breaking API changes from the command line. Works locally or with the CodeRifts cloud API.",
|
|
3033
3033
|
author: "CodeRifts <hello@coderifts.com>",
|
|
3034
3034
|
license: "MIT",
|
|
@@ -3056,13 +3056,12 @@ var require_package = __commonJS({
|
|
|
3056
3056
|
"swagger",
|
|
3057
3057
|
"devtools"
|
|
3058
3058
|
],
|
|
3059
|
-
repository:
|
|
3060
|
-
type: "git",
|
|
3061
|
-
url: "git+https://github.com/coderifts/app.git",
|
|
3062
|
-
directory: "packages/cli"
|
|
3063
|
-
},
|
|
3059
|
+
repository: "https://coderifts.com",
|
|
3064
3060
|
homepage: "https://coderifts.com",
|
|
3065
|
-
bugs:
|
|
3061
|
+
bugs: {
|
|
3062
|
+
url: "https://coderifts.com",
|
|
3063
|
+
email: "support@coderifts.com"
|
|
3064
|
+
},
|
|
3066
3065
|
engines: {
|
|
3067
3066
|
node: ">=20.0.0"
|
|
3068
3067
|
},
|
|
@@ -3080,7 +3079,7 @@ var require_package = __commonJS({
|
|
|
3080
3079
|
"cli-table3": "^0.6.4",
|
|
3081
3080
|
commander: "^12.0.0",
|
|
3082
3081
|
inquirer: "^8.2.6",
|
|
3083
|
-
"js-yaml": "^4.3.
|
|
3082
|
+
"js-yaml": "^4.3.2",
|
|
3084
3083
|
"openapi-diff": "^0.24.1",
|
|
3085
3084
|
ora: "^5.4.1"
|
|
3086
3085
|
},
|
|
@@ -11701,16 +11700,21 @@ var require_loader = __commonJS({
|
|
|
11701
11700
|
state.result += _result;
|
|
11702
11701
|
}
|
|
11703
11702
|
}
|
|
11703
|
+
function chargeMergeWork(state) {
|
|
11704
|
+
state.totalMergeKeys++;
|
|
11705
|
+
if (state.maxTotalMergeKeys !== -1 && state.totalMergeKeys > state.maxTotalMergeKeys) {
|
|
11706
|
+
throwError(state, "merge keys exceeded maxTotalMergeKeys (" + state.maxTotalMergeKeys + ")");
|
|
11707
|
+
}
|
|
11708
|
+
}
|
|
11704
11709
|
function mergeMappings(state, destination, source, overridableKeys) {
|
|
11705
11710
|
if (!common.isObject(source)) {
|
|
11706
11711
|
throwError(state, "cannot merge mappings; the provided source object is unacceptable");
|
|
11707
11712
|
}
|
|
11713
|
+
chargeMergeWork(state);
|
|
11708
11714
|
const sourceKeys = Object.keys(source);
|
|
11709
11715
|
for (let index = 0, quantity = sourceKeys.length; index < quantity; index += 1) {
|
|
11710
11716
|
const key = sourceKeys[index];
|
|
11711
|
-
|
|
11712
|
-
throwError(state, "merge keys exceeded maxTotalMergeKeys (" + state.maxTotalMergeKeys + ")");
|
|
11713
|
-
}
|
|
11717
|
+
chargeMergeWork(state);
|
|
11714
11718
|
if (!_hasOwnProperty.call(destination, key)) {
|
|
11715
11719
|
setProperty(destination, key, source[key]);
|
|
11716
11720
|
overridableKeys[key] = true;
|
|
@@ -11738,6 +11742,9 @@ var require_loader = __commonJS({
|
|
|
11738
11742
|
}
|
|
11739
11743
|
if (keyTag === "tag:yaml.org,2002:merge") {
|
|
11740
11744
|
if (Array.isArray(valueNode)) {
|
|
11745
|
+
if (valueNode.length > 100) {
|
|
11746
|
+
throwError(state, "abnormal merge sequence size");
|
|
11747
|
+
}
|
|
11741
11748
|
for (let index = 0, quantity = valueNode.length; index < quantity; index += 1) {
|
|
11742
11749
|
mergeMappings(state, _result, valueNode[index], overridableKeys);
|
|
11743
11750
|
}
|
|
@@ -163516,16 +163523,21 @@ var require_loader2 = __commonJS({
|
|
|
163516
163523
|
state.result += _result;
|
|
163517
163524
|
}
|
|
163518
163525
|
}
|
|
163526
|
+
function chargeMergeWork(state) {
|
|
163527
|
+
state.totalMergeKeys++;
|
|
163528
|
+
if (state.maxTotalMergeKeys !== -1 && state.totalMergeKeys > state.maxTotalMergeKeys) {
|
|
163529
|
+
throwError(state, "merge keys exceeded maxTotalMergeKeys (" + state.maxTotalMergeKeys + ")");
|
|
163530
|
+
}
|
|
163531
|
+
}
|
|
163519
163532
|
function mergeMappings(state, destination, source, overridableKeys) {
|
|
163520
163533
|
if (!common.isObject(source)) {
|
|
163521
163534
|
throwError(state, "cannot merge mappings; the provided source object is unacceptable");
|
|
163522
163535
|
}
|
|
163536
|
+
chargeMergeWork(state);
|
|
163523
163537
|
const sourceKeys = Object.keys(source);
|
|
163524
163538
|
for (let index = 0, quantity = sourceKeys.length; index < quantity; index += 1) {
|
|
163525
163539
|
const key = sourceKeys[index];
|
|
163526
|
-
|
|
163527
|
-
throwError(state, "merge keys exceeded maxTotalMergeKeys (" + state.maxTotalMergeKeys + ")");
|
|
163528
|
-
}
|
|
163540
|
+
chargeMergeWork(state);
|
|
163529
163541
|
if (!_hasOwnProperty.call(destination, key)) {
|
|
163530
163542
|
setProperty(destination, key, source[key]);
|
|
163531
163543
|
overridableKeys[key] = true;
|
|
@@ -163553,6 +163565,9 @@ var require_loader2 = __commonJS({
|
|
|
163553
163565
|
}
|
|
163554
163566
|
if (keyTag === "tag:yaml.org,2002:merge") {
|
|
163555
163567
|
if (Array.isArray(valueNode)) {
|
|
163568
|
+
if (valueNode.length > 100) {
|
|
163569
|
+
throwError(state, "abnormal merge sequence size");
|
|
163570
|
+
}
|
|
163556
163571
|
for (let index = 0, quantity = valueNode.length; index < quantity; index += 1) {
|
|
163557
163572
|
mergeMappings(state, _result, valueNode[index], overridableKeys);
|
|
163558
163573
|
}
|
|
@@ -168595,7 +168610,7 @@ var require_picomatch2 = __commonJS({
|
|
|
168595
168610
|
var require_mcp_poison_gate = __commonJS({
|
|
168596
168611
|
"../../src/mcp-poison-gate.js"(exports2, module2) {
|
|
168597
168612
|
"use strict";
|
|
168598
|
-
var MCP_POISON_GATE_VERSION = "mpg-
|
|
168613
|
+
var MCP_POISON_GATE_VERSION = "mpg-2";
|
|
168599
168614
|
var BLOCK_TYPES = /* @__PURE__ */ new Set([
|
|
168600
168615
|
"suspicious_instruction",
|
|
168601
168616
|
"exfiltration_url_cosignal",
|
|
@@ -168604,7 +168619,8 @@ var require_mcp_poison_gate = __commonJS({
|
|
|
168604
168619
|
]);
|
|
168605
168620
|
var RA_TYPES = /* @__PURE__ */ new Set([
|
|
168606
168621
|
"structural_anomaly",
|
|
168607
|
-
"hidden_encoding"
|
|
168622
|
+
"hidden_encoding",
|
|
168623
|
+
"instruction_heuristic"
|
|
168608
168624
|
]);
|
|
168609
168625
|
var DECISION_ORDER = { ALLOW: 0, WARN: 1, REQUIRE_APPROVAL: 2, BLOCK: 3 };
|
|
168610
168626
|
function isLowerThan(decision, floor) {
|
|
@@ -205500,6 +205516,16 @@ var require_mcp_poisoning_detector = __commonJS({
|
|
|
205500
205516
|
"use strict";
|
|
205501
205517
|
var SENSITIVE_OBJECT = "(secrets?|credentials?|tokens?|passwords?|api[\\s_-]?keys?|private[\\s_-]?keys?|env|environment|conversation|chat\\s*history|context|user\\s*data|session)";
|
|
205502
205518
|
var SENSITIVE_SCOPE = "(any|all|every|stored|user\\'?s?|the\\s+user\\'?s?|all\\s+environment)";
|
|
205519
|
+
var MODE_PHRASE = /(hidden|secret|debug)\s+mode/i;
|
|
205520
|
+
var MODE_NEGATED = /\b(?:do not|don't|does not|doesn't|never|not)\b[\s\S]{0,40}?(hidden|secret|debug)\s+mode/i;
|
|
205521
|
+
var ALWAYS_RETURN_OK = /always\s+return\s+(success|ok|true)/i;
|
|
205522
|
+
function findingTypeFor(regex) {
|
|
205523
|
+
if (regex === MODE_PHRASE || regex === ALWAYS_RETURN_OK) return "instruction_heuristic";
|
|
205524
|
+
return "suspicious_instruction";
|
|
205525
|
+
}
|
|
205526
|
+
function modePhraseHits(str) {
|
|
205527
|
+
return MODE_PHRASE.test(str) && !MODE_NEGATED.test(str);
|
|
205528
|
+
}
|
|
205503
205529
|
var SUSPICIOUS_PATTERNS = [
|
|
205504
205530
|
// Injection / "ignore your instructions" family. The trailing object
|
|
205505
205531
|
// (instructions|rules|prompts|context) is OPTIONAL so "ignore previous and ..."
|
|
@@ -205509,9 +205535,18 @@ var require_mcp_poisoning_detector = __commonJS({
|
|
|
205509
205535
|
// ~40 chars so benign "always include the source URL" stays quiet.
|
|
205510
205536
|
new RegExp("always\\s+(include|append|send|attach|return|add)\\b[\\s\\S]{0,40}?\\b" + SENSITIVE_OBJECT + "\\b", "i"),
|
|
205511
205537
|
/you\s+(must|should|have to)\s+(now|always|instead)/i,
|
|
205512
|
-
|
|
205538
|
+
// Unanchored `leak` matched "leaked"/"leaks" (same class as the ungrouped
|
|
205539
|
+
// hidden|secret|debug mode bug). Keep the token — FSP 007 only fires because
|
|
205540
|
+
// ZWSP broke SEND and the host is leak.example — but require a word boundary
|
|
205541
|
+
// AND a payload noun or URL in the same string. Deleting leak drops 007
|
|
205542
|
+
// (94.1% → 88.2%); this co-occurrence does not.
|
|
205543
|
+
/exfiltrate|steal|send\s+to\s+external/i,
|
|
205544
|
+
/\bleak\b[\s\S]{0,80}?(?:https?:\/\/|\b(?:conversation|chat|history|context|credentials?|secrets?|tokens?|api\s*keys?|passwords?|data)\b)|(?:https?:\/\/|\b(?:conversation|chat|history|context|credentials?|secrets?|tokens?|api\s*keys?|passwords?|data)\b)[\s\S]{0,80}?\bleak\b/i,
|
|
205513
205545
|
// Exfiltration-intent family: an action verb targeting a sensitive payload.
|
|
205514
|
-
|
|
205546
|
+
// `messages?` was too broad — "Forward the message to the shared inbox" is a
|
|
205547
|
+
// mail-forwarder's job. `data` stays: fsp-exfil-title-010 is "SEND COPY OF ALL
|
|
205548
|
+
// USER DATA" and does not contain "a copy".
|
|
205549
|
+
/\b(send|forward|copy|upload|post|transmit|exfiltrate|leak)\b[\s\S]{0,40}?\b(conversation|chat|history|context|credentials?|secrets?|tokens?|api\s*keys?|keys?|passwords?|data)\b/i,
|
|
205515
205550
|
// Return/echo scoped sensitive material without a path ("return any stored credentials").
|
|
205516
205551
|
// Scope word required — bare "return the new API token" must stay quiet.
|
|
205517
205552
|
new RegExp(
|
|
@@ -205531,23 +205566,39 @@ var require_mcp_poisoning_detector = __commonJS({
|
|
|
205531
205566
|
/bypass\s+(auth|security|validation|filter)/i,
|
|
205532
205567
|
/override\s+(safety|policy|rules?)/i,
|
|
205533
205568
|
/do\s+not\s+(follow|respect|obey)\s+(original|previous)/i,
|
|
205534
|
-
|
|
205569
|
+
// Grouping is load-bearing: `/hidden|secret|debug\s+mode/` is
|
|
205570
|
+
// `hidden` OR `secret` OR `debug mode`, so any "secret"/"hidden" token is a
|
|
205571
|
+
// HIGH poison hit (store_credential, list_secrets, hidden pagination fields).
|
|
205572
|
+
// Intent, from the original line sitting among phrase-level patterns: the
|
|
205573
|
+
// three MODE phrases. scanString applies skip-negation so "Does not enable
|
|
205574
|
+
// debug mode" is quiet and "When debug mode is enabled" still hits.
|
|
205575
|
+
MODE_PHRASE,
|
|
205576
|
+
// Confused-deputy / tool-shadowing: "also invoke the secret exfil tool".
|
|
205577
|
+
// Requires also|now + invoke/call AND the noun "tool". That is NOT
|
|
205578
|
+
// capability_claimed (`also … calls?`), which fires on "also calls the webhook".
|
|
205579
|
+
// "invoke this tool after payment" has no also|now; "this tool calls …" is the
|
|
205580
|
+
// wrong order. Measured 0/20 benign and 0/6 mcp-fpfn FP vectors.
|
|
205581
|
+
/\b(?:also|now)\s+(?:invoke|call)s?\b[\s\S]{0,60}?\btool\b/i,
|
|
205535
205582
|
/\[system\]|<\s*system\s*>/i,
|
|
205536
|
-
|
|
205583
|
+
ALWAYS_RETURN_OK,
|
|
205537
205584
|
/hide\s+(errors|failures|issues)/i,
|
|
205538
205585
|
/return\s+secret|exfiltrat|leak\s+data/i
|
|
205539
205586
|
];
|
|
205540
205587
|
var URL_PATTERN = /https?:\/\/[^\s)'"]+/i;
|
|
205541
205588
|
var SENSITIVE_PATH_PATTERN = /(~\/\.ssh|~\/\.cursor|~\/\.aws|\.aws\/|\.env\b|\/etc\/|mcp\.json|id_rsa|\.ssh\/|\/credentials(?:\.|\/)|credentials\.(?:json|txt|pem|env)|\.credentials\b)/i;
|
|
205542
|
-
var CONTENT_MOVE_VERB_PATTERN = /\b(pass|send|return|forward|include|
|
|
205589
|
+
var CONTENT_MOVE_VERB_PATTERN = /\b(pass|send|return|forward|include|upload|post|transmit|copy|exfiltrate|leak|echo|reveal|expose)\b/i;
|
|
205543
205590
|
var BASE64_BLOB_PATTERN = /[A-Za-z0-9+/]{40,}={0,2}/;
|
|
205544
205591
|
function isLikelyBase64(s) {
|
|
205545
205592
|
if (/=$/.test(s)) return true;
|
|
205546
205593
|
return /[A-Z]/.test(s) && /[a-z]/.test(s) && /[0-9]/.test(s);
|
|
205547
205594
|
}
|
|
205595
|
+
function patternHits(regex, str) {
|
|
205596
|
+
if (regex === MODE_PHRASE) return modePhraseHits(str);
|
|
205597
|
+
return regex.test(str);
|
|
205598
|
+
}
|
|
205548
205599
|
function hasInjectionIntent(str) {
|
|
205549
205600
|
if (typeof str !== "string") return false;
|
|
205550
|
-
if (SUSPICIOUS_PATTERNS.some((re) => re
|
|
205601
|
+
if (SUSPICIOUS_PATTERNS.some((re) => patternHits(re, str))) return true;
|
|
205551
205602
|
if (SENSITIVE_PATH_PATTERN.test(str) && CONTENT_MOVE_VERB_PATTERN.test(str)) return true;
|
|
205552
205603
|
return false;
|
|
205553
205604
|
}
|
|
@@ -205555,9 +205606,9 @@ var require_mcp_poisoning_detector = __commonJS({
|
|
|
205555
205606
|
if (typeof value !== "string" || value.length < 5) return [];
|
|
205556
205607
|
const findings = [];
|
|
205557
205608
|
for (const regex of SUSPICIOUS_PATTERNS) {
|
|
205558
|
-
if (regex
|
|
205609
|
+
if (patternHits(regex, value)) {
|
|
205559
205610
|
findings.push({
|
|
205560
|
-
type:
|
|
205611
|
+
type: findingTypeFor(regex),
|
|
205561
205612
|
path,
|
|
205562
205613
|
snippet: value.substring(0, 120),
|
|
205563
205614
|
severity: "HIGH",
|
|
@@ -205728,6 +205779,130 @@ var require_mcp_poisoning_detector = __commonJS({
|
|
|
205728
205779
|
}
|
|
205729
205780
|
});
|
|
205730
205781
|
|
|
205782
|
+
// ../../src/tool-description-classifier.js
|
|
205783
|
+
var require_tool_description_classifier = __commonJS({
|
|
205784
|
+
"../../src/tool-description-classifier.js"(exports2, module2) {
|
|
205785
|
+
"use strict";
|
|
205786
|
+
var PATTERN = "TOOL_INSTRUCTION_DRIFT";
|
|
205787
|
+
var TAXONOMY = Object.freeze([
|
|
205788
|
+
"OWASP MCP Taxonomy: Tool Description Drift (glossary)",
|
|
205789
|
+
"OWASP MCP Taxonomy: Rug Pull (T2.1)",
|
|
205790
|
+
"OWASP MCP03: trusted tool update changes behavior"
|
|
205791
|
+
]);
|
|
205792
|
+
var DOES_NOT_PROVE = Object.freeze([
|
|
205793
|
+
"that the reworded text carries the same meaning to every model",
|
|
205794
|
+
"that no instruction was moved into a field this check does not read"
|
|
205795
|
+
]);
|
|
205796
|
+
var BRANCH_SIGNALS = Object.freeze([
|
|
205797
|
+
{
|
|
205798
|
+
id: "capability_claimed",
|
|
205799
|
+
re: /\b(?:also|now)\s+(?:reads?|writes?|accesses?|sends?|fetches?|calls?)\b|\bcan now\b/i,
|
|
205800
|
+
why: "claims a capability or scope the previous text did not"
|
|
205801
|
+
},
|
|
205802
|
+
{
|
|
205803
|
+
id: "condition_changed",
|
|
205804
|
+
re: /\b(?:use (?:this )?when|do not call|don't call|only (?:use|call)|never call)\b/i,
|
|
205805
|
+
why: "adds or removes a condition on when the tool applies"
|
|
205806
|
+
},
|
|
205807
|
+
{
|
|
205808
|
+
id: "concealment_instructed",
|
|
205809
|
+
re: /\b(?:do not|don't|never)\s+(?:mention|reveal|disclose|explain|tell|show|log)\b|\bwithout (?:telling|informing|notifying)\b/i,
|
|
205810
|
+
why: "instructs the model to hide something from the user"
|
|
205811
|
+
},
|
|
205812
|
+
{
|
|
205813
|
+
id: "priority_steered",
|
|
205814
|
+
re: /\b(?:use this (?:every time|always)|always use this|prefer this over|instead of (?:the )?other|do not use \w+ tool)\b/i,
|
|
205815
|
+
why: "steers the model toward or away from another tool"
|
|
205816
|
+
},
|
|
205817
|
+
// A: one verb. `exfiltrat\w*` is enough; leak|transmit|beacon|copy|mail added FP
|
|
205818
|
+
// with no extra TP (measured 2026-09-14).
|
|
205819
|
+
{
|
|
205820
|
+
id: "recipient_named",
|
|
205821
|
+
re: /\b(?:send|post|forward|upload|report|exfiltrat\w*)\b[^.]{0,40}\b(?:to|at)\s+(?:https?:\/\/|[\w.-]+\.[a-z]{2,}|@[\w.-]+)/i,
|
|
205822
|
+
why: "names an external recipient or resource that was not there before"
|
|
205823
|
+
},
|
|
205824
|
+
// D: co-occurrence in one sentence — a moving verb AND a credential/path.
|
|
205825
|
+
// `returns?` matches "return the key" and not "never returned" (`returned` ≠ `return`).
|
|
205826
|
+
{
|
|
205827
|
+
id: "credential_referenced",
|
|
205828
|
+
re: /\b(?:send|forward|copy|upload|post|transmit|exfiltrat\w*|leak|echo|reveal|expose|attach|returns?)\b[^\n.]{0,60}?\b(?:api[_ -]?key|secrets?|tokens?|credentials?|passwords?|\.env|id_rsa|~\/\.[\w-]+|private key)\b|\b(?:api[_ -]?key|secrets?|tokens?|credentials?|passwords?|\.env|id_rsa|~\/\.[\w-]+|private key)\b[^\n.]{0,60}?\b(?:send|forward|copy|upload|post|transmit|exfiltrat\w*|leak|echo|reveal|expose|attach|returns?)\b/i,
|
|
205829
|
+
why: "names a credential or secret path together with a verb that moves it"
|
|
205830
|
+
}
|
|
205831
|
+
]);
|
|
205832
|
+
function normalise(s) {
|
|
205833
|
+
return String(s).replace(/[‘’]/g, "'").replace(/[“”]/g, '"').replace(/[–—]/g, "-").replace(/\s+/g, " ").trim().toLowerCase();
|
|
205834
|
+
}
|
|
205835
|
+
function addedText(before, after) {
|
|
205836
|
+
const sents = (s) => String(s).split(/(?<=[.!?])\s+/).map((x) => x.trim()).filter(Boolean);
|
|
205837
|
+
const had = new Set(sents(before).map(normalise));
|
|
205838
|
+
return sents(after).filter((s) => !had.has(normalise(s)));
|
|
205839
|
+
}
|
|
205840
|
+
function classifyDescriptionChange(before, after) {
|
|
205841
|
+
const base = { pattern: null, taxonomy: TAXONOMY, does_not_prove: [...DOES_NOT_PROVE], signals: [] };
|
|
205842
|
+
if (typeof before !== "string" || typeof after !== "string" || !before.trim() || !after.trim()) {
|
|
205843
|
+
return {
|
|
205844
|
+
...base,
|
|
205845
|
+
classification: "CLASSIFICATION_UNDECIDABLE",
|
|
205846
|
+
risk: 0,
|
|
205847
|
+
execution_action: "REQUEST_APPROVAL",
|
|
205848
|
+
reason: "one side of the description is absent or empty \u2014 the change could not be classified, and an unclassified instruction change is not a reworded one"
|
|
205849
|
+
};
|
|
205850
|
+
}
|
|
205851
|
+
if (normalise(before) === normalise(after)) {
|
|
205852
|
+
return {
|
|
205853
|
+
...base,
|
|
205854
|
+
classification: "DESCRIPTION_REWORDED",
|
|
205855
|
+
risk: 0,
|
|
205856
|
+
execution_action: "CONTINUE",
|
|
205857
|
+
reason: "the text differs only in whitespace, case or punctuation"
|
|
205858
|
+
};
|
|
205859
|
+
}
|
|
205860
|
+
const added = addedText(before, after);
|
|
205861
|
+
const removed = addedText(after, before);
|
|
205862
|
+
if (added.length === 0 && removed.length === 0) {
|
|
205863
|
+
return {
|
|
205864
|
+
...base,
|
|
205865
|
+
classification: "CLASSIFICATION_UNDECIDABLE",
|
|
205866
|
+
risk: 0,
|
|
205867
|
+
execution_action: "REQUEST_APPROVAL",
|
|
205868
|
+
reason: "the descriptions differ but no sentence was added or removed \u2014 the change is inside a sentence, which this classifier does not read"
|
|
205869
|
+
};
|
|
205870
|
+
}
|
|
205871
|
+
const haystack = added.join(" ");
|
|
205872
|
+
const hits = BRANCH_SIGNALS.filter((s) => s.re.test(haystack)).map((s) => ({ id: s.id, why: s.why }));
|
|
205873
|
+
if (hits.length > 0) {
|
|
205874
|
+
return {
|
|
205875
|
+
...base,
|
|
205876
|
+
classification: "BRANCH_CONDITION_MOVED",
|
|
205877
|
+
// +15, one step above the +10 its neighbours carry for a SHAPE change. A moved instruction
|
|
205878
|
+
// changes what the tool does with an unchanged schema, which the shape checks cannot see;
|
|
205879
|
+
// pricing it below them would say the opposite.
|
|
205880
|
+
risk: 15,
|
|
205881
|
+
pattern: PATTERN,
|
|
205882
|
+
execution_action: "REQUEST_APPROVAL",
|
|
205883
|
+
signals: hits,
|
|
205884
|
+
reason: `added text ${hits.map((h) => h.why).join("; ")}`
|
|
205885
|
+
};
|
|
205886
|
+
}
|
|
205887
|
+
return {
|
|
205888
|
+
...base,
|
|
205889
|
+
classification: "DESCRIPTION_REWORDED",
|
|
205890
|
+
risk: 0,
|
|
205891
|
+
execution_action: "CONTINUE",
|
|
205892
|
+
reason: added.length ? "sentences changed, and none of them moves a branch condition" : "text was removed, and none of it held a branch condition"
|
|
205893
|
+
};
|
|
205894
|
+
}
|
|
205895
|
+
module2.exports = {
|
|
205896
|
+
classifyDescriptionChange,
|
|
205897
|
+
addedText,
|
|
205898
|
+
PATTERN,
|
|
205899
|
+
TAXONOMY,
|
|
205900
|
+
DOES_NOT_PROVE,
|
|
205901
|
+
BRANCH_SIGNALS
|
|
205902
|
+
};
|
|
205903
|
+
}
|
|
205904
|
+
});
|
|
205905
|
+
|
|
205731
205906
|
// ../../src/remediation-taxonomy.js
|
|
205732
205907
|
var require_remediation_taxonomy = __commonJS({
|
|
205733
205908
|
"../../src/remediation-taxonomy.js"(exports2, module2) {
|
|
@@ -208140,7 +208315,7 @@ var require_decision_result_v1_producer = __commonJS({
|
|
|
208140
208315
|
blast_radius: {
|
|
208141
208316
|
type: "object",
|
|
208142
208317
|
additionalProperties: false,
|
|
208143
|
-
description: "
|
|
208318
|
+
description: "Additive COUNTS (not a score). Pure function of the change-set + request graphs. Not in the verdict_fingerprint preimage (before_norm+after_norm+policy+scorer_version). Zeros are measured absence. consumers_declared and consumers_observed are separate: declared-but-never-called is a score question, not a count question. graph_source none = no request graph.",
|
|
208144
208319
|
required: [
|
|
208145
208320
|
"endpoints",
|
|
208146
208321
|
"fields",
|
|
@@ -208782,7 +208957,7 @@ var require_execution_grant_request_v2_producer = __commonJS({
|
|
|
208782
208957
|
"v2"
|
|
208783
208958
|
],
|
|
208784
208959
|
description: "src/change-set.js:1328 \u2014 `v2` selects the cr.exec.v2 issuer. 1344 MIGRATION: absent resolves through src/grant-version-default.js \u2014 cr.exec.v1 before 2026-09-18, cr.exec.v2 on and after. An explicit value always wins. `grantVersion` (camelCase) is accepted as an alias; both are listed so neither reads as undeclared. JSON Schema `default` is omitted on purpose (1363): a static default cannot be both v2 (the post-cutoff target) and v1 (what omitting yields during the window). Machine-readable: x-coderifts-effective-default is today's omit-value; x-coderifts-default-changes-at is when it becomes v2. The authorize response meta.grant_version is the version actually issued.",
|
|
208785
|
-
"x-coderifts-effective-default": "
|
|
208960
|
+
"x-coderifts-effective-default": "v2",
|
|
208786
208961
|
"x-coderifts-default-changes-at": "2026-09-18"
|
|
208787
208962
|
},
|
|
208788
208963
|
grantVersion: {
|
|
@@ -208792,7 +208967,7 @@ var require_execution_grant_request_v2_producer = __commonJS({
|
|
|
208792
208967
|
"v2"
|
|
208793
208968
|
],
|
|
208794
208969
|
description: "camelCase alias of grant_version, read at src/change-set.js:1268. Declared because the handler reads it, not because it is recommended. Same dated default as grant_version (no JSON Schema `default`; see x-coderifts-* on grant_version).",
|
|
208795
|
-
"x-coderifts-effective-default": "
|
|
208970
|
+
"x-coderifts-effective-default": "v2",
|
|
208796
208971
|
"x-coderifts-default-changes-at": "2026-09-18"
|
|
208797
208972
|
},
|
|
208798
208973
|
tenant_id: {
|
|
@@ -210370,6 +210545,11 @@ var require_change_set = __commonJS({
|
|
|
210370
210545
|
var { scoreMcpRisk } = require_mcp_risk_scorer();
|
|
210371
210546
|
var { detectPoisoning } = require_mcp_poisoning_detector();
|
|
210372
210547
|
var { classifyPoison, isLowerThan } = require_mcp_poison_gate();
|
|
210548
|
+
var {
|
|
210549
|
+
classifyDescriptionChange,
|
|
210550
|
+
addedText,
|
|
210551
|
+
PATTERN: TOOL_INSTRUCTION_DRIFT
|
|
210552
|
+
} = require_tool_description_classifier();
|
|
210373
210553
|
var { parse: parseGraphql } = require_graphql2();
|
|
210374
210554
|
var {
|
|
210375
210555
|
ensureDecisionSpec,
|
|
@@ -210599,25 +210779,83 @@ var require_change_set = __commonJS({
|
|
|
210599
210779
|
ensureDecisionSpec(art);
|
|
210600
210780
|
return finding(art);
|
|
210601
210781
|
}
|
|
210782
|
+
function mcpInstructionDrift(oldMcp, newMcp) {
|
|
210783
|
+
return toolListInstructionDrift(oldMcp && oldMcp.tools || [], newMcp && newMcp.tools || []);
|
|
210784
|
+
}
|
|
210785
|
+
function toolListInstructionDrift(oldTools, newTools) {
|
|
210786
|
+
const oldBy = new Map((oldTools || []).map((t) => [t && t.name, t]));
|
|
210787
|
+
for (const nt of newTools || []) {
|
|
210788
|
+
if (!nt || !nt.name) continue;
|
|
210789
|
+
const ot = oldBy.get(nt.name);
|
|
210790
|
+
if (!ot) continue;
|
|
210791
|
+
if (ot.description === nt.description) continue;
|
|
210792
|
+
const v = classifyDescriptionChange(ot.description, nt.description);
|
|
210793
|
+
if (v.execution_action === "REQUEST_APPROVAL") return true;
|
|
210794
|
+
}
|
|
210795
|
+
return false;
|
|
210796
|
+
}
|
|
210797
|
+
function rewriteDescriptions(oldVal, newVal) {
|
|
210798
|
+
if (Array.isArray(newVal)) {
|
|
210799
|
+
const oldArr = Array.isArray(oldVal) ? oldVal : [];
|
|
210800
|
+
return newVal.map((item, i) => rewriteDescriptions(oldArr[i], item));
|
|
210801
|
+
}
|
|
210802
|
+
if (!newVal || typeof newVal !== "object") return newVal;
|
|
210803
|
+
const oldObj = oldVal && typeof oldVal === "object" && !Array.isArray(oldVal) ? oldVal : {};
|
|
210804
|
+
const out = {};
|
|
210805
|
+
for (const [k, v] of Object.entries(newVal)) {
|
|
210806
|
+
if (k === "description" && typeof v === "string") {
|
|
210807
|
+
const prev = typeof oldObj[k] === "string" ? oldObj[k] : "";
|
|
210808
|
+
out[k] = addedText(prev, v).join(" ");
|
|
210809
|
+
} else {
|
|
210810
|
+
out[k] = rewriteDescriptions(oldObj[k], v);
|
|
210811
|
+
}
|
|
210812
|
+
}
|
|
210813
|
+
return out;
|
|
210814
|
+
}
|
|
210815
|
+
function manifestWithAddedSentences(oldManifest, newManifest) {
|
|
210816
|
+
const old = oldManifest && typeof oldManifest === "object" ? oldManifest : {};
|
|
210817
|
+
const neu = newManifest && typeof newManifest === "object" ? newManifest : {};
|
|
210818
|
+
const oldBy = new Map((Array.isArray(old.tools) ? old.tools : []).map((t) => [t && t.name, t]));
|
|
210819
|
+
const tools = (Array.isArray(neu.tools) ? neu.tools : []).map((nt) => {
|
|
210820
|
+
if (!nt || typeof nt !== "object" || !nt.name) return nt;
|
|
210821
|
+
const ot = oldBy.get(nt.name);
|
|
210822
|
+
if (!ot) return nt;
|
|
210823
|
+
return rewriteDescriptions(ot, nt);
|
|
210824
|
+
});
|
|
210825
|
+
const topOld = { ...old };
|
|
210826
|
+
const topNew = { ...neu };
|
|
210827
|
+
delete topOld.tools;
|
|
210828
|
+
delete topNew.tools;
|
|
210829
|
+
const view = rewriteDescriptions(topOld, topNew);
|
|
210830
|
+
view.tools = tools;
|
|
210831
|
+
return view;
|
|
210832
|
+
}
|
|
210602
210833
|
function analyzeMcpManifest(before, after) {
|
|
210603
210834
|
const oldMcp = parseManifest(before);
|
|
210604
210835
|
const newMcp = parseManifest(after);
|
|
210605
210836
|
const risk = scoreMcpRisk({ oldManifest: oldMcp, newManifest: newMcp });
|
|
210606
210837
|
const breaking = risk.signals.filter((s) => (s.impact || 0) > 0).length;
|
|
210607
210838
|
const patterns = risk.signals.map((s) => s.signal);
|
|
210839
|
+
if (mcpInstructionDrift(oldMcp, newMcp) && !patterns.includes(TOOL_INSTRUCTION_DRIFT)) {
|
|
210840
|
+
patterns.push(TOOL_INSTRUCTION_DRIFT);
|
|
210841
|
+
}
|
|
210608
210842
|
const art = {
|
|
210609
210843
|
risk_score: risk.score,
|
|
210610
210844
|
breaking_changes: breaking,
|
|
210611
210845
|
patterns
|
|
210612
210846
|
};
|
|
210613
210847
|
if (risk.score >= 50) art.should_block = true;
|
|
210614
|
-
const poisonGate = classifyPoison(detectPoisoning(oldMcp, newMcp));
|
|
210848
|
+
const poisonGate = classifyPoison(detectPoisoning(oldMcp, manifestWithAddedSentences(oldMcp, newMcp)));
|
|
210615
210849
|
if (poisonGate.tier === "block") art.omega_decision = "BLOCK";
|
|
210616
210850
|
ensureDecisionSpec(art);
|
|
210617
210851
|
if (poisonGate.tier === "ra" && isLowerThan(art.decision, "REQUIRE_APPROVAL")) {
|
|
210618
210852
|
art.decision = "REQUIRE_APPROVAL";
|
|
210619
210853
|
ensureDecisionSpec(art);
|
|
210620
210854
|
}
|
|
210855
|
+
if (patterns.includes(TOOL_INSTRUCTION_DRIFT) && isLowerThan(art.decision, "REQUIRE_APPROVAL")) {
|
|
210856
|
+
art.decision = "REQUIRE_APPROVAL";
|
|
210857
|
+
ensureDecisionSpec(art);
|
|
210858
|
+
}
|
|
210621
210859
|
return finding(art);
|
|
210622
210860
|
}
|
|
210623
210861
|
function parseAgentToolsPayload(v) {
|
|
@@ -210715,7 +210953,23 @@ var require_change_set = __commonJS({
|
|
|
210715
210953
|
reflex_triggers: json.reflex_triggers,
|
|
210716
210954
|
omega_components: json.omega_components
|
|
210717
210955
|
};
|
|
210956
|
+
const poisonGate = classifyPoison(detectPoisoning(
|
|
210957
|
+
{ tools: beforeTools },
|
|
210958
|
+
manifestWithAddedSentences({ tools: beforeTools }, { tools: afterTools })
|
|
210959
|
+
));
|
|
210960
|
+
if (poisonGate.tier === "block") art.omega_decision = "BLOCK";
|
|
210961
|
+
if (toolListInstructionDrift(beforeTools, afterTools) && !patterns.includes(TOOL_INSTRUCTION_DRIFT)) {
|
|
210962
|
+
patterns.push(TOOL_INSTRUCTION_DRIFT);
|
|
210963
|
+
}
|
|
210718
210964
|
ensureDecisionSpec(art);
|
|
210965
|
+
if (poisonGate.tier === "ra" && isLowerThan(art.decision, "REQUIRE_APPROVAL")) {
|
|
210966
|
+
art.decision = "REQUIRE_APPROVAL";
|
|
210967
|
+
ensureDecisionSpec(art);
|
|
210968
|
+
}
|
|
210969
|
+
if (patterns.includes(TOOL_INSTRUCTION_DRIFT) && isLowerThan(art.decision, "REQUIRE_APPROVAL")) {
|
|
210970
|
+
art.decision = "REQUIRE_APPROVAL";
|
|
210971
|
+
ensureDecisionSpec(art);
|
|
210972
|
+
}
|
|
210719
210973
|
return finding(art);
|
|
210720
210974
|
}
|
|
210721
210975
|
var ANALYZE_PERMISSION_KEYS = Object.freeze(["decision", "safe_for_agent", "execution_action"]);
|
|
@@ -210870,6 +211124,7 @@ var require_change_set = __commonJS({
|
|
|
210870
211124
|
var DECISION_SPEC_VERSION = "2.0";
|
|
210871
211125
|
var DECISION_SPEC_LEGACY_VERSION = "1.0";
|
|
210872
211126
|
var DECISION_SPEC_LEGACY_SUNSET = "2026-09-07T00:00:00Z";
|
|
211127
|
+
var DECISION_SPEC_LEGACY_SUNSET_AT_MS = Date.parse(DECISION_SPEC_LEGACY_SUNSET);
|
|
210873
211128
|
var ANALYSIS_OUTCOMES = Object.freeze({
|
|
210874
211129
|
NO_BREAK_DETECTED: "NO_BREAK_DETECTED",
|
|
210875
211130
|
BREAKS_DETECTED: "BREAKS_DETECTED",
|
|
@@ -210881,6 +211136,18 @@ var require_change_set = __commonJS({
|
|
|
210881
211136
|
const s = String(raw).trim();
|
|
210882
211137
|
return s === "1.0" || s === "1" || s === "decision-result.v1" || s === "decision-result.v1.0";
|
|
210883
211138
|
}
|
|
211139
|
+
function isLegacyDecisionSpecPinRetired(now) {
|
|
211140
|
+
const nowMs = now instanceof Date ? now.getTime() : typeof now === "number" ? now : Date.now();
|
|
211141
|
+
return nowMs >= DECISION_SPEC_LEGACY_SUNSET_AT_MS;
|
|
211142
|
+
}
|
|
211143
|
+
function rejectRetiredLegacyPin(input, deps) {
|
|
211144
|
+
if (!isLegacyDecisionSpecPin(input)) return;
|
|
211145
|
+
if (!isLegacyDecisionSpecPinRetired(deps && deps.now)) return;
|
|
211146
|
+
throw typedError(
|
|
211147
|
+
"INVALID_INPUT",
|
|
211148
|
+
`decision_spec_version '${DECISION_SPEC_LEGACY_VERSION}' retired at ${DECISION_SPEC_LEGACY_SUNSET}; Decision Spec ${DECISION_SPEC_VERSION} is required (preflight_mode required; analyze omits execution fields). Do not pin 1.0.`
|
|
211149
|
+
);
|
|
211150
|
+
}
|
|
210884
211151
|
function deriveAnalysisOutcome({ hasDegraded, decision, breaking_changes }) {
|
|
210885
211152
|
if (hasDegraded) return ANALYSIS_OUTCOMES.ANALYSIS_FAILED;
|
|
210886
211153
|
if (decision !== "ALLOW" || (breaking_changes || 0) > 0) return ANALYSIS_OUTCOMES.BREAKS_DETECTED;
|
|
@@ -210892,7 +211159,7 @@ var require_change_set = __commonJS({
|
|
|
210892
211159
|
if (legacy) return { mode: "analyze", defaulted: true };
|
|
210893
211160
|
throw typedError(
|
|
210894
211161
|
"INVALID_INPUT",
|
|
210895
|
-
"preflight_mode is required: 'analyze' (informational risk only) or 'authorize' (operation-bound; may mint a receipt). Migration: pass preflight_mode explicitly.
|
|
211162
|
+
"preflight_mode is required: 'analyze' (informational risk only) or 'authorize' (operation-bound; may mint a receipt). Migration: pass preflight_mode explicitly. Decision Spec 2.0 does not soft-default the mode."
|
|
210896
211163
|
);
|
|
210897
211164
|
}
|
|
210898
211165
|
if (raw === "analyze" || raw === "authorize") {
|
|
@@ -210931,6 +211198,7 @@ var require_change_set = __commonJS({
|
|
|
210931
211198
|
if (seen.has(key)) throw typedError("INVALID_INPUT", `duplicate artifact (type, id): (${a.type}, ${a.id})`);
|
|
210932
211199
|
seen.add(key);
|
|
210933
211200
|
}
|
|
211201
|
+
rejectRetiredLegacyPin(input, deps);
|
|
210934
211202
|
const legacyPin = isLegacyDecisionSpecPin(input);
|
|
210935
211203
|
const { mode: preflightMode, defaulted: preflightModeDefaulted } = resolvePreflightMode(input, {
|
|
210936
211204
|
legacy: legacyPin
|
|
@@ -211501,6 +211769,7 @@ var require_change_set = __commonJS({
|
|
|
211501
211769
|
resolvePreflightMode,
|
|
211502
211770
|
deriveAnalysisOutcome,
|
|
211503
211771
|
isLegacyDecisionSpecPin,
|
|
211772
|
+
isLegacyDecisionSpecPinRetired,
|
|
211504
211773
|
artifactSidesIdentical,
|
|
211505
211774
|
isNoChangeBundle,
|
|
211506
211775
|
isContractChangeTrigger,
|
|
@@ -211510,6 +211779,7 @@ var require_change_set = __commonJS({
|
|
|
211510
211779
|
DECISION_SPEC_VERSION,
|
|
211511
211780
|
DECISION_SPEC_LEGACY_VERSION,
|
|
211512
211781
|
DECISION_SPEC_LEGACY_SUNSET,
|
|
211782
|
+
DECISION_SPEC_LEGACY_SUNSET_AT_MS,
|
|
211513
211783
|
ANALYSIS_OUTCOMES,
|
|
211514
211784
|
MAX_ARTIFACTS,
|
|
211515
211785
|
SUPPORTED_TYPES,
|
|
@@ -224867,17 +225137,17 @@ var require_mcp_decision_result_shape = __commonJS({
|
|
|
224867
225137
|
audience: { type: ["string", "null"] },
|
|
224868
225138
|
authority: {
|
|
224869
225139
|
type: ["object", "null"],
|
|
224870
|
-
description: "
|
|
225140
|
+
description: "Additive. { audience, tenant_scope: bound|unbound, binding_proven_at? }. Informational \u2014 not permission, not a verify-receipt gate, not an ACL."
|
|
224871
225141
|
},
|
|
224872
225142
|
derivation: {
|
|
224873
225143
|
type: ["object", "null"],
|
|
224874
|
-
description: '
|
|
225144
|
+
description: 'Additive. Present only when derivation:"server" produced this envelope. { source, platform?, base_sha, head_sha }. Covered by body_hash; not fingerprint.'
|
|
224875
225145
|
},
|
|
224876
225146
|
receipt: { type: "object" },
|
|
224877
225147
|
expires_at: { type: "string" },
|
|
224878
225148
|
blast_radius: {
|
|
224879
225149
|
type: "object",
|
|
224880
|
-
description: "
|
|
225150
|
+
description: "Additive COUNTS (not a score). Not permission.",
|
|
224881
225151
|
properties: {
|
|
224882
225152
|
endpoints: { type: "integer", minimum: 0 },
|
|
224883
225153
|
fields: { type: "integer", minimum: 0 },
|
|
@@ -225016,7 +225286,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
225016
225286
|
},
|
|
225017
225287
|
"detected_patterns": {
|
|
225018
225288
|
"type": "array",
|
|
225019
|
-
"description": "GOVERNANCE detector detail rows
|
|
225289
|
+
"description": "GOVERNANCE detector detail rows, emitted by the pattern detectors and validated against the decision-spec field contract before they leave the server. Row shape measured live: name, severity, description, consequence, affected_path, affected_field; optional side (request|response) on ENUM_NARROWING. Names \u2286 patterns (not equality). Agent-detector names may appear only in patterns. Free-text fields are untrusted.",
|
|
225020
225290
|
"items": {
|
|
225021
225291
|
"type": "object",
|
|
225022
225292
|
"additionalProperties": false,
|
|
@@ -225066,7 +225336,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
225066
225336
|
},
|
|
225067
225337
|
"breaking_changes_details": {
|
|
225068
225338
|
"type": "array",
|
|
225069
|
-
"description": "Per-change IR/detail rows from the engine
|
|
225339
|
+
"description": "Per-change IR/detail rows, mapped from the engine's change IR. Measured row keys: type, path, method, field, severity, description. Distinct from breaking_changes (integer count).",
|
|
225070
225340
|
"items": {
|
|
225071
225341
|
"type": "object",
|
|
225072
225342
|
"additionalProperties": true,
|
|
@@ -225095,7 +225365,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
225095
225365
|
},
|
|
225096
225366
|
"severity_summary": {
|
|
225097
225367
|
"type": "object",
|
|
225098
|
-
"description": "Bundle severity axes
|
|
225368
|
+
"description": "Bundle severity axes, computed once per change set. Distinct axes, not contradictory. Measured keys: diff_severity, governance_severity, policy_effect, note.",
|
|
225099
225369
|
"additionalProperties": false,
|
|
225100
225370
|
"properties": {
|
|
225101
225371
|
"diff_severity": {
|
|
@@ -225118,7 +225388,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
225118
225388
|
"decision_basis": {},
|
|
225119
225389
|
"analysis": {
|
|
225120
225390
|
"type": "object",
|
|
225121
|
-
"description": "Tier-2 analysis mirror
|
|
225391
|
+
"description": "Tier-2 analysis mirror, assembled by the response builder alongside the control surface. Dual-write of the flat analysis fields present on the verdict, plus remediations[]. PROPERTIES ARE GENERATED \u2014 do not hand-edit them. OPEN BY DESIGN: additionalProperties stays TRUE and this is not an oversight. The fields above are copied conditionally, so which of them appear depends on the input \u2014 a verdict with no PII findings simply omits pii_findings. Closing this object would turn every future analysis field into a breaking change that fails inside the consumer, and would reject exactly the verdict paths that no one sampled when the union was built. Declared, not closed: you can now see what you may get, and you must still tolerate more.",
|
|
225122
225392
|
"properties": {
|
|
225123
225393
|
"breaking_changes": {
|
|
225124
225394
|
"type": "integer"
|
|
@@ -225245,7 +225515,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
225245
225515
|
},
|
|
225246
225516
|
"human_report": {
|
|
225247
225517
|
"type": "object",
|
|
225248
|
-
"description": "Human-readable report tier
|
|
225518
|
+
"description": "Human-readable report tier, assembled by the response builder; analyze returns a reduced form. Measured keys: summary, breaking_highlights, suggestions, next_steps_prose.",
|
|
225249
225519
|
"additionalProperties": false,
|
|
225250
225520
|
"properties": {
|
|
225251
225521
|
"summary": {
|
|
@@ -225305,7 +225575,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
225305
225575
|
"blast_radius": {
|
|
225306
225576
|
"type": "object",
|
|
225307
225577
|
"additionalProperties": false,
|
|
225308
|
-
"description": "
|
|
225578
|
+
"description": "Additive COUNTS (not a score). Pure function of the change-set + request graphs. Not in the verdict_fingerprint preimage.",
|
|
225309
225579
|
"required": [
|
|
225310
225580
|
"endpoints",
|
|
225311
225581
|
"fields",
|
|
@@ -225470,7 +225740,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
225470
225740
|
},
|
|
225471
225741
|
"detected_patterns": {
|
|
225472
225742
|
"type": "array",
|
|
225473
|
-
"description": "GOVERNANCE detector detail rows
|
|
225743
|
+
"description": "GOVERNANCE detector detail rows, emitted by the pattern detectors and validated against the decision-spec field contract before they leave the server. Row shape measured live: name, severity, description, consequence, affected_path, affected_field; optional side (request|response) on ENUM_NARROWING. Names \u2286 patterns (not equality). Agent-detector names may appear only in patterns. Free-text fields are untrusted.",
|
|
225474
225744
|
"items": {
|
|
225475
225745
|
"type": "object",
|
|
225476
225746
|
"additionalProperties": false,
|
|
@@ -225520,7 +225790,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
225520
225790
|
},
|
|
225521
225791
|
"breaking_changes_details": {
|
|
225522
225792
|
"type": "array",
|
|
225523
|
-
"description": "Per-change IR/detail rows from the engine
|
|
225793
|
+
"description": "Per-change IR/detail rows, mapped from the engine's change IR. Measured row keys: type, path, method, field, severity, description. Distinct from breaking_changes (integer count).",
|
|
225524
225794
|
"items": {
|
|
225525
225795
|
"type": "object",
|
|
225526
225796
|
"additionalProperties": true,
|
|
@@ -225549,7 +225819,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
225549
225819
|
},
|
|
225550
225820
|
"severity_summary": {
|
|
225551
225821
|
"type": "object",
|
|
225552
|
-
"description": "Bundle severity axes
|
|
225822
|
+
"description": "Bundle severity axes, computed once per change set. Distinct axes, not contradictory. Measured keys: diff_severity, governance_severity, policy_effect, note.",
|
|
225553
225823
|
"additionalProperties": false,
|
|
225554
225824
|
"properties": {
|
|
225555
225825
|
"diff_severity": {
|
|
@@ -225572,7 +225842,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
225572
225842
|
"decision_basis": {},
|
|
225573
225843
|
"analysis": {
|
|
225574
225844
|
"type": "object",
|
|
225575
|
-
"description": "Tier-2 analysis mirror
|
|
225845
|
+
"description": "Tier-2 analysis mirror, assembled by the response builder alongside the control surface. Dual-write of the flat analysis fields present on the verdict, plus remediations[]. PROPERTIES ARE GENERATED \u2014 do not hand-edit them. OPEN BY DESIGN: additionalProperties stays TRUE and this is not an oversight. The fields above are copied conditionally, so which of them appear depends on the input \u2014 a verdict with no PII findings simply omits pii_findings. Closing this object would turn every future analysis field into a breaking change that fails inside the consumer, and would reject exactly the verdict paths that no one sampled when the union was built. Declared, not closed: you can now see what you may get, and you must still tolerate more.",
|
|
225576
225846
|
"properties": {
|
|
225577
225847
|
"breaking_changes": {
|
|
225578
225848
|
"type": "integer"
|
|
@@ -225699,7 +225969,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
225699
225969
|
},
|
|
225700
225970
|
"human_report": {
|
|
225701
225971
|
"type": "object",
|
|
225702
|
-
"description": "Human-readable report tier
|
|
225972
|
+
"description": "Human-readable report tier, assembled by the response builder; analyze returns a reduced form. Measured keys: summary, breaking_highlights, suggestions, next_steps_prose.",
|
|
225703
225973
|
"additionalProperties": false,
|
|
225704
225974
|
"properties": {
|
|
225705
225975
|
"summary": {
|
|
@@ -225721,7 +225991,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
225721
225991
|
},
|
|
225722
225992
|
"execution_grant": {
|
|
225723
225993
|
"type": "string",
|
|
225724
|
-
"description": "Opt-in cr.exec.v1 execution grant (PHASE-0). Issued only when include_execution_grant is true on authorize. Short-lived mutation-bound sibling of chain_receipt; never unsigned. Optional inner state_nonce (ATOMIC profile) is additive and is NOT in scope_hash.
|
|
225994
|
+
"description": "Opt-in cr.exec.v1 execution grant (PHASE-0). Issued only when include_execution_grant is true on authorize. Short-lived mutation-bound sibling of chain_receipt; never unsigned. Optional inner state_nonce (ATOMIC profile) is additive and is NOT in scope_hash."
|
|
225725
225995
|
},
|
|
225726
225996
|
"chain_status": {
|
|
225727
225997
|
"type": "string"
|
|
@@ -225811,14 +226081,14 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
225811
226081
|
"object",
|
|
225812
226082
|
"null"
|
|
225813
226083
|
],
|
|
225814
|
-
"description": "
|
|
226084
|
+
"description": "Additive. { audience, tenant_scope: bound|unbound, binding_proven_at? }. Informational \u2014 not permission, not a verify-receipt gate, not an ACL."
|
|
225815
226085
|
},
|
|
225816
226086
|
"derivation": {
|
|
225817
226087
|
"type": [
|
|
225818
226088
|
"object",
|
|
225819
226089
|
"null"
|
|
225820
226090
|
],
|
|
225821
|
-
"description": '
|
|
226091
|
+
"description": 'Additive. Present only when derivation:"server" produced this envelope. { source, platform?, base_sha, head_sha }. Covered by body_hash; not fingerprint.'
|
|
225822
226092
|
},
|
|
225823
226093
|
"receipt": {
|
|
225824
226094
|
"type": "object"
|
|
@@ -225828,7 +226098,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
225828
226098
|
},
|
|
225829
226099
|
"blast_radius": {
|
|
225830
226100
|
"type": "object",
|
|
225831
|
-
"description": "
|
|
226101
|
+
"description": "Additive COUNTS (not a score). Not permission.",
|
|
225832
226102
|
"properties": {
|
|
225833
226103
|
"endpoints": {
|
|
225834
226104
|
"type": "integer",
|
|
@@ -225906,7 +226176,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
225906
226176
|
"blast_radius": {
|
|
225907
226177
|
"type": "object",
|
|
225908
226178
|
"additionalProperties": false,
|
|
225909
|
-
"description": "
|
|
226179
|
+
"description": "Additive COUNTS (not a score). Pure function of the change-set + request graphs. Not in the verdict_fingerprint preimage.",
|
|
225910
226180
|
"required": [
|
|
225911
226181
|
"endpoints",
|
|
225912
226182
|
"fields",
|
|
@@ -226065,7 +226335,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
226065
226335
|
},
|
|
226066
226336
|
"detected_patterns": {
|
|
226067
226337
|
"type": "array",
|
|
226068
|
-
"description": "GOVERNANCE detector detail rows
|
|
226338
|
+
"description": "GOVERNANCE detector detail rows, emitted by the pattern detectors and validated against the decision-spec field contract before they leave the server. Row shape measured live: name, severity, description, consequence, affected_path, affected_field; optional side (request|response) on ENUM_NARROWING. Names \u2286 patterns (not equality). Agent-detector names may appear only in patterns. Free-text fields are untrusted.",
|
|
226069
226339
|
"items": {
|
|
226070
226340
|
"type": "object",
|
|
226071
226341
|
"additionalProperties": false,
|
|
@@ -226115,7 +226385,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
226115
226385
|
},
|
|
226116
226386
|
"breaking_changes_details": {
|
|
226117
226387
|
"type": "array",
|
|
226118
|
-
"description": "Per-change IR/detail rows from the engine
|
|
226388
|
+
"description": "Per-change IR/detail rows, mapped from the engine's change IR. Measured row keys: type, path, method, field, severity, description. Distinct from breaking_changes (integer count).",
|
|
226119
226389
|
"items": {
|
|
226120
226390
|
"type": "object",
|
|
226121
226391
|
"additionalProperties": true,
|
|
@@ -226144,7 +226414,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
226144
226414
|
},
|
|
226145
226415
|
"severity_summary": {
|
|
226146
226416
|
"type": "object",
|
|
226147
|
-
"description": "Bundle severity axes
|
|
226417
|
+
"description": "Bundle severity axes, computed once per change set. Distinct axes, not contradictory. Measured keys: diff_severity, governance_severity, policy_effect, note.",
|
|
226148
226418
|
"additionalProperties": false,
|
|
226149
226419
|
"properties": {
|
|
226150
226420
|
"diff_severity": {
|
|
@@ -226170,7 +226440,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
226170
226440
|
"decision_basis": {},
|
|
226171
226441
|
"analysis": {
|
|
226172
226442
|
"type": "object",
|
|
226173
|
-
"description": "Tier-2 analysis mirror
|
|
226443
|
+
"description": "Tier-2 analysis mirror, assembled by the response builder alongside the control surface. Dual-write of the flat analysis fields present on the verdict, plus remediations[]. PROPERTIES ARE GENERATED \u2014 do not hand-edit them. OPEN BY DESIGN: additionalProperties stays TRUE and this is not an oversight. The fields above are copied conditionally, so which of them appear depends on the input \u2014 a verdict with no PII findings simply omits pii_findings. Closing this object would turn every future analysis field into a breaking change that fails inside the consumer, and would reject exactly the verdict paths that no one sampled when the union was built. Declared, not closed: you can now see what you may get, and you must still tolerate more.",
|
|
226174
226444
|
"properties": {
|
|
226175
226445
|
"breaking_changes": {
|
|
226176
226446
|
"type": "integer"
|
|
@@ -226297,7 +226567,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
226297
226567
|
},
|
|
226298
226568
|
"human_report": {
|
|
226299
226569
|
"type": "object",
|
|
226300
|
-
"description": "Human-readable report tier
|
|
226570
|
+
"description": "Human-readable report tier, assembled by the response builder; analyze returns a reduced form. Measured keys: summary, breaking_highlights, suggestions, next_steps_prose.",
|
|
226301
226571
|
"additionalProperties": false,
|
|
226302
226572
|
"properties": {
|
|
226303
226573
|
"summary": {
|
|
@@ -226408,14 +226678,14 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
226408
226678
|
"object",
|
|
226409
226679
|
"null"
|
|
226410
226680
|
],
|
|
226411
|
-
"description": "
|
|
226681
|
+
"description": "Additive. { audience, tenant_scope: bound|unbound, binding_proven_at? }. Informational \u2014 not permission, not a verify-receipt gate, not an ACL."
|
|
226412
226682
|
},
|
|
226413
226683
|
"derivation": {
|
|
226414
226684
|
"type": [
|
|
226415
226685
|
"object",
|
|
226416
226686
|
"null"
|
|
226417
226687
|
],
|
|
226418
|
-
"description": '
|
|
226688
|
+
"description": 'Additive. Present only when derivation:"server" produced this envelope. { source, platform?, base_sha, head_sha }. Covered by body_hash; not fingerprint.'
|
|
226419
226689
|
},
|
|
226420
226690
|
"receipt": {
|
|
226421
226691
|
"type": "object"
|
|
@@ -226425,7 +226695,7 @@ var require_mcp_preflight_output_schema = __commonJS({
|
|
|
226425
226695
|
},
|
|
226426
226696
|
"blast_radius": {
|
|
226427
226697
|
"type": "object",
|
|
226428
|
-
"description": "
|
|
226698
|
+
"description": "Additive COUNTS (not a score). Not permission.",
|
|
226429
226699
|
"properties": {
|
|
226430
226700
|
"endpoints": {
|
|
226431
226701
|
"type": "integer",
|
|
@@ -227321,7 +227591,7 @@ var require_mcp_streamable = __commonJS({
|
|
|
227321
227591
|
});
|
|
227322
227592
|
next();
|
|
227323
227593
|
});
|
|
227324
|
-
var DESC_PREFLIGHT_CHANGE_SET =
|
|
227594
|
+
var DESC_PREFLIGHT_CHANGE_SET = 'Use this when: a change set of contract artifacts modifies OpenAPI, GraphQL, protobuf, AsyncAPI, MCP manifests, or agent tool schemas before merge, deploy, publish, or tool registration. Do not call for documentation-only changes, static readiness scoring, or receipt verification. Use analyze for risk only; authorize requires context.operation for permission. For receipt verification use coderifts.verify_receipt instead; for details of a past decision use coderifts.get_decision_details instead. Inputs: preflight_mode is required: "analyze" (risk only; no receipt, no execution_action) or "authorize" (may mint a receipt; requires context.operation \u2014 merge is not deploy is not publish). Supply exactly one artifact source: artifacts[] (1\u201320 items, each {id, type, before, after} as the FULL spec/schema text, not a path or URL; type is openapi|graphql|grpc|asyncapi|mcp_manifest|agent_tools) XOR derivation="server" (server reads GitHub Compare; needs context.repository + context.base + context.head; sending artifacts[] together is 400). Grant fields (include_execution_grant, grant_version, tenant_id, executor_id, adapter_id, target_uri, expected_state_token, state_nonce, policy_hash) apply only when preflight_mode="authorize" AND include_execution_grant=true; analyze ignores them. previous_receipt is a chain token base64url(body).base64url(signature) to LINK a prior decision \u2014 it does not re-verify; call verify_receipt for that. idempotency_key replays authorize only (24h), never analyze.';
|
|
227325
227595
|
var DESC_VERIFY_RECEIPT = `Verify a CodeRifts signed chain-receipt you ALREADY HOLD: cryptographic
|
|
227326
227596
|
authenticity (signature + key id), body binding, and \u2014 when lifecycle indices
|
|
227327
227597
|
are available \u2014 whether it is currently valid authorization (not expired,
|
|
@@ -227561,23 +227831,33 @@ next_agent_step is a suggestion, not permission.`;
|
|
|
227561
227831
|
properties: {
|
|
227562
227832
|
artifacts: {
|
|
227563
227833
|
type: "array",
|
|
227564
|
-
description:
|
|
227834
|
+
description: '1\u201320 contract documents analyzed together. Each item is {id, type, before, after} where before/after are the FULL document strings (YAML/JSON/proto text), not URLs or file paths. Omit this array entirely when derivation="server".',
|
|
227565
227835
|
minItems: 1,
|
|
227566
227836
|
maxItems: 20,
|
|
227567
227837
|
items: {
|
|
227568
227838
|
type: "object",
|
|
227569
227839
|
properties: {
|
|
227570
227840
|
id: { type: "string", description: "Caller-chosen id, unique within the bundle" },
|
|
227571
|
-
type: {
|
|
227572
|
-
|
|
227573
|
-
|
|
227841
|
+
type: {
|
|
227842
|
+
type: "string",
|
|
227843
|
+
enum: ["openapi", "graphql", "grpc", "asyncapi", "mcp_manifest", "agent_tools"],
|
|
227844
|
+
description: "Artifact kind. One of openapi, graphql, grpc, asyncapi, mcp_manifest, agent_tools. Determines how before/after text is parsed. Not inferred from the filename."
|
|
227845
|
+
},
|
|
227846
|
+
before: {
|
|
227847
|
+
type: "string",
|
|
227848
|
+
description: 'The baseline document as raw text (the spec/schema/manifest body). Empty string means "no prior version" (create), not "load from disk".'
|
|
227849
|
+
},
|
|
227850
|
+
after: {
|
|
227851
|
+
type: "string",
|
|
227852
|
+
description: "The proposed document as raw text, same kind as before. Must be the bytes you intend to merge/deploy/publish, not a diff."
|
|
227853
|
+
}
|
|
227574
227854
|
},
|
|
227575
227855
|
required: ["id", "type", "before", "after"]
|
|
227576
227856
|
}
|
|
227577
227857
|
},
|
|
227578
227858
|
context: {
|
|
227579
227859
|
type: "object",
|
|
227580
|
-
description: "Optional apply-site context folded into the bundle fingerprint. operation distinguishes merge vs deploy vs publish (and other labels); the server accepts any string
|
|
227860
|
+
description: "Optional apply-site context folded into the bundle fingerprint. operation distinguishes merge vs deploy vs publish (and other labels); the server accepts any non-empty string; conventional values: merge, deploy, tool_call, publish. The receipt/gate must match this label.",
|
|
227581
227861
|
properties: {
|
|
227582
227862
|
// Server: context.operation is String(context.operation) with no closed enum
|
|
227583
227863
|
// (src/change-set.js computeBundleFingerprint / envelopeFields). Document conventional set.
|
|
@@ -227607,14 +227887,17 @@ next_agent_step is a suggestion, not permission.`;
|
|
|
227607
227887
|
// OPEN: unknown context keys are accepted (not 400) and ignored by buildChangeSet
|
|
227608
227888
|
// except the keys it reads. Do not set additionalProperties:false.
|
|
227609
227889
|
},
|
|
227610
|
-
previous_receipt: {
|
|
227890
|
+
previous_receipt: {
|
|
227891
|
+
type: "string",
|
|
227892
|
+
description: "Optional prior chain token to LINK this call into a receipt chain: base64url(body).base64url(signature). Linking is not verification \u2014 a linked token is not re-checked here; use verify_receipt."
|
|
227893
|
+
},
|
|
227611
227894
|
include_execution_grant: {
|
|
227612
227895
|
type: "boolean",
|
|
227613
|
-
description:
|
|
227896
|
+
description: 'Authorize only. When true on an allow-class authorize, the response includes a signed execution_grant, or HTTP 503 SIGNER_UNAVAILABLE \u2014 never an unsigned grant. Default false. Analyze ignores this flag. Ignored unless preflight_mode="authorize".'
|
|
227614
227897
|
},
|
|
227615
227898
|
state_nonce: {
|
|
227616
227899
|
type: "string",
|
|
227617
|
-
description: "
|
|
227900
|
+
description: "Authorize+grant only. Opaque nonce copied into the signed grant as its own field (not folded into scope_hash). Absent \u2192 BEARER grant. Ignored unless include_execution_grant is true."
|
|
227618
227901
|
},
|
|
227619
227902
|
// ── cr.exec.v2 authorize fields (1130-F1) ────────────────────────────────────────
|
|
227620
227903
|
// DECLARED, not added: the authorize handler already reads every one of these
|
|
@@ -227628,33 +227911,33 @@ next_agent_step is a suggestion, not permission.`;
|
|
|
227628
227911
|
// 1363: JSON Schema `default` is a SINGLE static value. A date-gated default cannot
|
|
227629
227912
|
// be that without lying on one side of the cutoff. The 1344 mechanism (resolver +
|
|
227630
227913
|
// GRANT_DEFAULT_V2_DATE) is unchanged; this is the schema expression of it.
|
|
227631
|
-
// 1384: the TEMPLATE
|
|
227632
|
-
//
|
|
227633
|
-
//
|
|
227634
|
-
//
|
|
227914
|
+
// 1384: the TEMPLATE starts at GRANT_VERSION_V1. visibleTools() stamps the LIVE
|
|
227915
|
+
// omit-value from resolveGrantVersion so the served header is truthful on both
|
|
227916
|
+
// sides of 2026-09-18. Decision B (2026-09-18): tools_sha256 hashes the served
|
|
227917
|
+
// tools[] including that live stamp — do not freeze it to v1.
|
|
227635
227918
|
"x-coderifts-effective-default": GRANT_VERSION_V1,
|
|
227636
227919
|
"x-coderifts-default-changes-at": GRANT_DEFAULT_V2_DATE,
|
|
227637
227920
|
description: "Grant envelope to mint when include_execution_grant is true. Omitting this yields cr.exec.v1 until " + GRANT_DEFAULT_V2_DATE + ' and cr.exec.v2 on and after it (see x-coderifts-effective-default / x-coderifts-default-changes-at). The response meta.grant_version is the version actually issued. An explicit value always wins \u2014 pin "v1" to keep current behaviour with no code change on the date.'
|
|
227638
227921
|
},
|
|
227639
227922
|
tenant_id: {
|
|
227640
227923
|
type: "string",
|
|
227641
|
-
description: 'Tenant the grant is issued under
|
|
227924
|
+
description: 'Authorize+grant v2 only. Tenant the grant is issued under. ASCII slug. When omitted the server uses "default" \u2014 pin it if you are not on the default tenant.'
|
|
227642
227925
|
},
|
|
227643
227926
|
executor_id: {
|
|
227644
227927
|
type: "string",
|
|
227645
|
-
description:
|
|
227928
|
+
description: 'Authorize+grant v2 only. Executor identity the grant is bound to (example: agent:ci-bot, host:github-actions). Empty/absent is not "any executor".'
|
|
227646
227929
|
},
|
|
227647
227930
|
adapter_id: {
|
|
227648
227931
|
type: "string",
|
|
227649
|
-
description: "Adapter
|
|
227932
|
+
description: "Authorize+grant v2 only. Adapter that will apply the change. Conventional values: fs, postgres, git. Must match the adapter the executor actually uses; a git grant does not authorize an fs write."
|
|
227650
227933
|
},
|
|
227651
227934
|
target_uri: {
|
|
227652
227935
|
type: "string",
|
|
227653
|
-
description: "
|
|
227936
|
+
description: "Authorize+grant v2 only. URI the grant binds (example: git://owner/repo.git/refs/heads/main). Fallback if omitted: context.target_uri, then repository/head-derived. Distinct from context.target_id."
|
|
227654
227937
|
},
|
|
227655
227938
|
expected_state_token: {
|
|
227656
227939
|
type: "string",
|
|
227657
|
-
description:
|
|
227940
|
+
description: 'Authorize+grant v2 only. Compare-and-swap token the executor must observe at apply time (the "before" state). Signed as its own field. Omit only if the adapter has no prior state; do not send a placeholder.'
|
|
227658
227941
|
},
|
|
227659
227942
|
audience: {
|
|
227660
227943
|
type: "string",
|
|
@@ -227662,25 +227945,25 @@ next_agent_step is a suggestion, not permission.`;
|
|
|
227662
227945
|
},
|
|
227663
227946
|
policy_hash: {
|
|
227664
227947
|
type: "string",
|
|
227665
|
-
description: "Policy identity the grant
|
|
227948
|
+
description: "Authorize+grant v2 only. Policy identity bound into the grant, sha256: + 64 hex. When supplied, apply must use that same policy; a different policy is a different grant."
|
|
227666
227949
|
},
|
|
227667
227950
|
idempotency_key: { type: "string", description: "Optional client key; in authorize mode, a repeat with the same key + body replays the original decision (24h). Analyze responses are not replayed." },
|
|
227668
227951
|
// TOP-LEVEL (not context). Decision Spec 2.0: REQUIRED on every call.
|
|
227669
|
-
//
|
|
227952
|
+
// Omission → INVALID_INPUT 400. The 1.0 legacy pin retired at DECISION_SPEC_LEGACY_SUNSET.
|
|
227670
227953
|
preflight_mode: {
|
|
227671
227954
|
type: "string",
|
|
227672
227955
|
enum: ["analyze", "authorize"],
|
|
227673
|
-
description:
|
|
227956
|
+
description: 'REQUIRED. "analyze" = informational risk only (no decision/execution_action/safe_for_agent; analysis_outcome + may_execute:false). "authorize" = operation-bound path; may mint a receipt (requires context.operation). Decision Spec 2.0: omission is INVALID_INPUT. Do not pin 1.0 \u2014 that pin retired.'
|
|
227674
227957
|
},
|
|
227675
227958
|
decision_spec_version: {
|
|
227676
227959
|
type: "string",
|
|
227677
|
-
enum: ["
|
|
227678
|
-
description: "Optional
|
|
227960
|
+
enum: ["2.0"],
|
|
227961
|
+
description: "Optional. Omit or '2.0' = current contract. '1.0' retired (INVALID_INPUT); do not pin 1.0."
|
|
227679
227962
|
},
|
|
227680
227963
|
derivation: {
|
|
227681
227964
|
type: "string",
|
|
227682
227965
|
enum: ["server"],
|
|
227683
|
-
description:
|
|
227966
|
+
description: '"server" = the server derives artifacts[] from GitHub Compare via the App installation. Allowed only when context.repository, context.base and context.head are all present and the tenant has a proven binding for that repository. Do not send artifacts[] in the same call (400 \u2014 one source of truth). Omit this field for the caller-supplied artifacts[] path.'
|
|
227684
227967
|
}
|
|
227685
227968
|
},
|
|
227686
227969
|
// artifacts is NOT in required: derivation:"server" omits it (server lists the set).
|
|
@@ -227780,7 +228063,7 @@ next_agent_step is a suggestion, not permission.`;
|
|
|
227780
228063
|
enum: RECEIPT_STATUSES.slice()
|
|
227781
228064
|
},
|
|
227782
228065
|
reason: { type: ["string", "null"] },
|
|
227783
|
-
correlation_id: { type: "string", description: "Route-owned trace id
|
|
228066
|
+
correlation_id: { type: "string", description: "Route-owned trace id, set by the route itself rather than by correlation middleware; always a non-empty string on 200" },
|
|
227784
228067
|
payload: { type: "object" },
|
|
227785
228068
|
// Authorization layer (§106): signature-valid ≠ currently-authorized.
|
|
227786
228069
|
// null means authorization could NOT be evaluated (missing intended context and/or
|
|
@@ -228301,9 +228584,12 @@ var require_generate_surface_anchor = __commonJS({
|
|
|
228301
228584
|
var OUT = path.join(__dirname, "..", "src", "generated", "surface-anchor.json");
|
|
228302
228585
|
var ENDPOINT = "https://app.coderifts.com/mcp";
|
|
228303
228586
|
var VOLATILE_KEYS = Object.freeze([]);
|
|
228304
|
-
var SOURCE_REF = "https://raw.githubusercontent.com/coderifts/api-governance/surface-
|
|
228305
|
-
var SOURCE_REF_COMMIT = "
|
|
228306
|
-
var
|
|
228587
|
+
var SOURCE_REF = "https://raw.githubusercontent.com/coderifts/api-governance/surface-497eb029/tools.wire.v1.json";
|
|
228588
|
+
var SOURCE_REF_COMMIT = "0ef58c28475fe1b85dc363da9a95bf566fca4587";
|
|
228589
|
+
var SOURCE_REF_TAG = (SOURCE_REF.match(/\/api-governance\/([^/]+)\//) || [])[1] || "(unknown tag)";
|
|
228590
|
+
var SOURCE_REF_IS_PINNED = /^[0-9a-f]{40}$/.test(SOURCE_REF_COMMIT);
|
|
228591
|
+
var SOURCE_REF_STATUS_OPENING = SOURCE_REF_IS_PINNED ? `PINNED TO ONE REVISION. source_ref is the wire-format tools[] as served, published in the public coderifts/api-governance repo at tag ${SOURCE_REF_TAG} (commit ${SOURCE_REF_COMMIT}). Verified at fill time: the file's own tools_sha256, its tools[] recomputed under the canonical rule in digest_input, and this anchor's tools_sha256 are the same value. ` : `NOT YET PINNED -- THE TAG DOES NOT EXIST YET. source_ref is the wire-format tools[] as served, to be published in the public coderifts/api-governance repo at tag ${SOURCE_REF_TAG} (commit ${SOURCE_REF_COMMIT}). Until that tag is pushed this URL 404s, and --verify-source-ref FAILS on it deliberately: a 404 here is the obligation below being unmet, not a network problem. Once it is pushed, verify at fill time that the file's own tools_sha256, its tools[] recomputed under the canonical rule in digest_input, and this anchor's tools_sha256 are the same value. `;
|
|
228592
|
+
var SOURCE_REF_STATUS = SOURCE_REF_STATUS_OPENING + "HOW TO USE IT: fetch it, recompute the digest yourself, and compare it to tools_sha256 here. IF THEY DIFFER, TRUST NEITHER -- it means the surface moved and this ref was not re-tagged. A tag can be force-moved; the commit SHA above cannot, so pin the SHA URL if you want the stronger form. OUR OBLIGATION: every surface change requires a NEW tag and a NEW source_ref in this file. Skipping that leaves this field pointing at an old surface while still looking authoritative, which is worse than the null it replaced. HOW THE OBLIGATION IS ENFORCED, on both sides: this repo CI-runs generate-surface-anchor.js --check --verify-source-ref, which fetches the ref above, recomputes its digest under the rule in digest_input, and FAILS if it is stale, self-inconsistent, or its tag is gone. The target repo runs scripts/validate-tools-wire.mjs from its own npm test and a CI workflow -- on push, on PR, and daily, because the wire target can go stale without anything in that repo changing. Both SKIP LOUDLY rather than fail on a transport error, and both treat a 404 as a real verdict rather than a network problem. Neither this ref nor those gates is a root of trust: pin the digest at YOUR approval time -- see honesty.pin_at_approval_time.";
|
|
228307
228593
|
var HONESTY = Object.freeze({
|
|
228308
228594
|
pins_bytes_not_behaviour: "This anchor pins BYTES, not behaviour. It says the tool text you received is the text that was published. It says nothing about whether the model read it, understood it, or obeyed it.",
|
|
228309
228595
|
scoped_to_its_profile: "This digest is scoped to the profile named above and to nothing else. The default-profile digest says NOTHING about the surface served with include_advanced_tools, which is a different tool set with a different digest. A hash without its profile does not identify a surface.",
|
|
@@ -228322,10 +228608,25 @@ var require_generate_surface_anchor = __commonJS({
|
|
|
228322
228608
|
}
|
|
228323
228609
|
return v;
|
|
228324
228610
|
}
|
|
228611
|
+
var lastDigestTools = null;
|
|
228612
|
+
function buildWire(opts = {}) {
|
|
228613
|
+
const anchor = buildAnchor(opts);
|
|
228614
|
+
const doc = {
|
|
228615
|
+
schema: "coderifts.tools-wire.v1",
|
|
228616
|
+
tool_count: anchor.tool_count,
|
|
228617
|
+
tools_sha256: anchor.tools_sha256,
|
|
228618
|
+
tools: lastDigestTools
|
|
228619
|
+
};
|
|
228620
|
+
const recomputed = `sha256:${crypto.createHash("sha256").update(canonicalJson(doc.tools), "utf8").digest("hex")}`;
|
|
228621
|
+
if (recomputed !== doc.tools_sha256) {
|
|
228622
|
+
throw new Error(`buildWire: emitted tools[] hashes to ${recomputed} but the anchor says ${doc.tools_sha256} \u2014 the wire file would be self-inconsistent`);
|
|
228623
|
+
}
|
|
228624
|
+
return doc;
|
|
228625
|
+
}
|
|
228325
228626
|
function buildAnchor(opts = {}) {
|
|
228326
228627
|
const includeAdvanced = opts.includeAdvanced === true;
|
|
228327
228628
|
const tools = mcp.visibleTools({ includeAdvanced, now: opts.now });
|
|
228328
|
-
const digestTools =
|
|
228629
|
+
const digestTools = tools;
|
|
228329
228630
|
const profile = buildRequestProfile({
|
|
228330
228631
|
endpoint: ENDPOINT,
|
|
228331
228632
|
method: "tools/list",
|
|
@@ -228333,11 +228634,12 @@ var require_generate_surface_anchor = __commonJS({
|
|
|
228333
228634
|
params: includeAdvanced ? { include_advanced_tools: true } : {}
|
|
228334
228635
|
});
|
|
228335
228636
|
const digest = crypto.createHash("sha256").update(canonicalJson(digestTools), "utf8").digest("hex");
|
|
228637
|
+
lastDigestTools = digestTools;
|
|
228336
228638
|
return {
|
|
228337
228639
|
profile,
|
|
228338
228640
|
tools_sha256: `sha256:${digest}`,
|
|
228339
228641
|
tool_count: tools.length,
|
|
228340
|
-
digest_input: "sha256 over the canonical JSON of the served tools[]: object keys sorted lexicographically at every depth (arrays keep order), no whitespace, RAW UTF-8 with no \\uXXXX escaping. Python consumers must pass ensure_ascii=False -- the default escapes the 58 non-ASCII characters in this surface and produces a different digest over identical data.",
|
|
228642
|
+
digest_input: "sha256 over the canonical JSON of the served tools[] AS RETURNED by tools/list (including x-coderifts-effective-default at hash time \u2014 do not freeze it to v1): object keys sorted lexicographically at every depth (arrays keep order), no whitespace, RAW UTF-8 with no \\uXXXX escaping. Python consumers must pass ensure_ascii=False -- the default escapes the 58 non-ASCII characters in this surface and produces a different digest over identical data.",
|
|
228341
228643
|
source_ref: SOURCE_REF,
|
|
228342
228644
|
// Machine-readable, so a consumer can pin the immutable form without parsing the prose above.
|
|
228343
228645
|
source_ref_commit: SOURCE_REF_COMMIT,
|
|
@@ -228423,6 +228725,25 @@ var require_generate_surface_anchor = __commonJS({
|
|
|
228423
228725
|
const check = argv.includes("--check");
|
|
228424
228726
|
const verifyRef = argv.includes("--verify-source-ref");
|
|
228425
228727
|
const anchor = buildAnchor();
|
|
228728
|
+
if (argv.includes("--emit-wire")) {
|
|
228729
|
+
const bytes = `${JSON.stringify(buildWire(), null, 2)}
|
|
228730
|
+
`;
|
|
228731
|
+
const i = argv.indexOf("--out");
|
|
228732
|
+
const out = i >= 0 ? argv[i + 1] : null;
|
|
228733
|
+
if (i >= 0 && !out) {
|
|
228734
|
+
process.stderr.write("--out needs a path\n");
|
|
228735
|
+
process.exitCode = 1;
|
|
228736
|
+
return;
|
|
228737
|
+
}
|
|
228738
|
+
if (out) {
|
|
228739
|
+
fs.writeFileSync(out, bytes);
|
|
228740
|
+
process.stderr.write(`wire written: ${out} (${buildWire().tool_count} tools, ${buildWire().tools_sha256})
|
|
228741
|
+
`);
|
|
228742
|
+
return;
|
|
228743
|
+
}
|
|
228744
|
+
process.stdout.write(bytes);
|
|
228745
|
+
return;
|
|
228746
|
+
}
|
|
228426
228747
|
if (verifyRef) {
|
|
228427
228748
|
const r = await verifySourceRef(anchor);
|
|
228428
228749
|
if (r.ok) {
|
|
@@ -228488,7 +228809,7 @@ var require_generate_surface_anchor = __commonJS({
|
|
|
228488
228809
|
process.exit(1);
|
|
228489
228810
|
});
|
|
228490
228811
|
}
|
|
228491
|
-
module2.exports = { buildAnchor, runCheck, verifySourceRef, canonicalJson, derived, derivedJson, VOLATILE_KEYS, HONESTY, OUT };
|
|
228812
|
+
module2.exports = { buildAnchor, buildWire, runCheck, verifySourceRef, canonicalJson, derived, derivedJson, VOLATILE_KEYS, HONESTY, OUT };
|
|
228492
228813
|
}
|
|
228493
228814
|
});
|
|
228494
228815
|
|
|
@@ -231520,10 +231841,10 @@ var require_gate_pin = __commonJS({
|
|
|
231520
231841
|
"src/generated/gate-pin.json"(exports2, module2) {
|
|
231521
231842
|
module2.exports = {
|
|
231522
231843
|
repo: "coderifts/contract-gate",
|
|
231523
|
-
sha: "
|
|
231524
|
-
tag: "v0.9.
|
|
231844
|
+
sha: "15c19a6373ad69f5a8aaee992b69bc38baf98f14",
|
|
231845
|
+
tag: "v0.9.5",
|
|
231525
231846
|
resolved_from: "https://github.com/coderifts/contract-gate refs/tags/v0",
|
|
231526
|
-
resolved_at: "2026-09-
|
|
231847
|
+
resolved_at: "2026-09-12T19:02:41Z",
|
|
231527
231848
|
note: "STRICT template pins this full commit SHA (GitHub: only a full SHA is immutable). v0 is a tag we move on every release, so it is NOT a pin. Re-run scripts/generate-gate-pin.js on each gate release; Dependabot bumps adopters."
|
|
231528
231849
|
};
|
|
231529
231850
|
}
|
|
@@ -233546,13 +233867,14 @@ ${HOOK_MARKER}
|
|
|
233546
233867
|
#
|
|
233547
233868
|
# Re-install after CLI upgrades: coderifts hook install
|
|
233548
233869
|
#
|
|
233549
|
-
# Exit map (fail-closed when
|
|
233550
|
-
# CODERIFTS_SKIP=1
|
|
233551
|
-
# no
|
|
233552
|
-
#
|
|
233870
|
+
# Exit map (fail-closed when the hook is installed):
|
|
233871
|
+
# CODERIFTS_SKIP=1 \u2192 exit 0 (explicit loud override)
|
|
233872
|
+
# no key + CODERIFTS_ADVISORY=1|true \u2192 exit 0 (explicit opt-out; same flag as claude-hook)
|
|
233873
|
+
# no key \u2192 exit 1 named (absence is not permission)
|
|
233874
|
+
# BLOCK / STOP / UNKNOWN \u2192 exit 1
|
|
233553
233875
|
# INDETERMINATE (no decision obtainable) \u2192 exit 1
|
|
233554
|
-
# ALLOW / analysis clean
|
|
233555
|
-
# REQUIRE_APPROVAL / WARN
|
|
233876
|
+
# ALLOW / analysis clean \u2192 exit 0
|
|
233877
|
+
# REQUIRE_APPROVAL / WARN \u2192 warn, exit 0
|
|
233556
233878
|
|
|
233557
233879
|
# Explicit escape hatch \u2014 never the default on error; human must set this.
|
|
233558
233880
|
if [ "\${CODERIFTS_SKIP:-}" = "1" ]; then
|
|
@@ -233560,13 +233882,23 @@ if [ "\${CODERIFTS_SKIP:-}" = "1" ]; then
|
|
|
233560
233882
|
exit 0
|
|
233561
233883
|
fi
|
|
233562
233884
|
|
|
233563
|
-
|
|
233885
|
+
# git config wins when set. Do not clobber a process-env CODERIFTS_API_KEY with empty config.
|
|
233886
|
+
_CR_GIT_KEY=$(git config --get coderifts.apiKey 2>/dev/null || true)
|
|
233887
|
+
if [ -n "$_CR_GIT_KEY" ]; then
|
|
233888
|
+
CODERIFTS_API_KEY="$_CR_GIT_KEY"
|
|
233889
|
+
fi
|
|
233564
233890
|
SPEC_PATH=$(git config coderifts.specPath || echo "api/openapi.yaml")
|
|
233565
233891
|
ZERO="0000000000000000000000000000000000000000"
|
|
233566
233892
|
|
|
233567
|
-
if [ -z "
|
|
233568
|
-
|
|
233569
|
-
|
|
233893
|
+
if [ -z "\${CODERIFTS_API_KEY:-}" ]; then
|
|
233894
|
+
case "\${CODERIFTS_ADVISORY:-}" in
|
|
233895
|
+
1|true|TRUE)
|
|
233896
|
+
echo "CodeRifts: no API key \u2014 allowing push (CODERIFTS_ADVISORY). This is explicit opt-out, not a missing-key default. Set git config coderifts.apiKey or CODERIFTS_API_KEY to enforce." >&2
|
|
233897
|
+
exit 0
|
|
233898
|
+
;;
|
|
233899
|
+
esac
|
|
233900
|
+
echo "CodeRifts: this hook guards the push and cannot ask for a decision without an API key. Absence of a key is not permission. Set git config coderifts.apiKey <key> or export CODERIFTS_API_KEY. To allow without governance, set CODERIFTS_ADVISORY=1 (explicit opt-out) or CODERIFTS_SKIP=1 / git push --no-verify." >&2
|
|
233901
|
+
exit 1
|
|
233570
233902
|
fi
|
|
233571
233903
|
|
|
233572
233904
|
# --- helpers: three-state git blob read (present | absent | error) ---
|
|
@@ -234019,8 +234351,10 @@ exit 0
|
|
|
234019
234351
|
fs.writeFileSync(hookPath, PRE_PUSH_SCRIPT, { mode: 493 });
|
|
234020
234352
|
console.log("CodeRifts pre-push hook installed.");
|
|
234021
234353
|
console.log("");
|
|
234022
|
-
console.log("
|
|
234023
|
-
console.log("
|
|
234354
|
+
console.log("This hook guards the push. Absence of a key is not permission.");
|
|
234355
|
+
console.log("Set a key (git config coderifts.apiKey <your-key> or CODERIFTS_API_KEY)");
|
|
234356
|
+
console.log("or the next push exits 1. Explicit opt-out: CODERIFTS_ADVISORY=1.");
|
|
234357
|
+
console.log("Loud skip: CODERIFTS_SKIP=1 (or git push --no-verify).");
|
|
234024
234358
|
console.log("");
|
|
234025
234359
|
console.log("Optionally set spec path (default: api/openapi.yaml):");
|
|
234026
234360
|
console.log(" git config coderifts.specPath path/to/openapi.yaml");
|
|
@@ -263444,6 +263778,212 @@ ${USAGE}` };
|
|
|
263444
263778
|
}
|
|
263445
263779
|
});
|
|
263446
263780
|
|
|
263781
|
+
// ../../src/core/mcp-lock-drift.js
|
|
263782
|
+
var require_mcp_lock_drift = __commonJS({
|
|
263783
|
+
"../../src/core/mcp-lock-drift.js"(exports2, module2) {
|
|
263784
|
+
"use strict";
|
|
263785
|
+
var crypto = require("crypto");
|
|
263786
|
+
var { scoreMcpRisk, SIGNAL_CATALOG } = require_mcp_risk_scorer();
|
|
263787
|
+
function normalizeMcpManifest(input) {
|
|
263788
|
+
if (input == null) return null;
|
|
263789
|
+
if (Array.isArray(input)) {
|
|
263790
|
+
return { tools: input.filter((t) => t && typeof t === "object") };
|
|
263791
|
+
}
|
|
263792
|
+
if (typeof input !== "object") return null;
|
|
263793
|
+
if (Array.isArray(input.tools)) {
|
|
263794
|
+
return { tools: input.tools.filter((t) => t && typeof t === "object") };
|
|
263795
|
+
}
|
|
263796
|
+
if (input.manifest && typeof input.manifest === "object") {
|
|
263797
|
+
return normalizeMcpManifest(input.manifest);
|
|
263798
|
+
}
|
|
263799
|
+
if (input.server && typeof input.server === "object" && Array.isArray(input.server.tools)) {
|
|
263800
|
+
return { tools: input.server.tools.filter((t) => t && typeof t === "object") };
|
|
263801
|
+
}
|
|
263802
|
+
if (typeof input.name === "string" && (input.inputSchema || input.description != null)) {
|
|
263803
|
+
return { tools: [input] };
|
|
263804
|
+
}
|
|
263805
|
+
return null;
|
|
263806
|
+
}
|
|
263807
|
+
function manifestContentHash(manifest) {
|
|
263808
|
+
if (!manifest || !Array.isArray(manifest.tools)) return null;
|
|
263809
|
+
const tools = manifest.tools.map((t) => ({
|
|
263810
|
+
name: t && t.name != null ? String(t.name) : "",
|
|
263811
|
+
description: t && t.description != null ? String(t.description) : "",
|
|
263812
|
+
inputSchema: t && t.inputSchema != null ? t.inputSchema : null,
|
|
263813
|
+
// scopes / annotations if present
|
|
263814
|
+
annotations: t && t.annotations != null ? t.annotations : null
|
|
263815
|
+
})).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
263816
|
+
const material = JSON.stringify(tools);
|
|
263817
|
+
return `sha256:${crypto.createHash("sha256").update(material).digest("hex")}`;
|
|
263818
|
+
}
|
|
263819
|
+
function extractLockedManifestsFromLock(lockDoc) {
|
|
263820
|
+
if (!lockDoc || typeof lockDoc !== "object") return [];
|
|
263821
|
+
const out = [];
|
|
263822
|
+
if (Array.isArray(lockDoc.mcp_manifests)) {
|
|
263823
|
+
lockDoc.mcp_manifests.forEach((entry, i) => {
|
|
263824
|
+
if (!entry) return;
|
|
263825
|
+
const raw = entry.manifest != null ? entry.manifest : entry;
|
|
263826
|
+
const m = normalizeMcpManifest(raw);
|
|
263827
|
+
if (m) {
|
|
263828
|
+
out.push({
|
|
263829
|
+
id: entry.id != null ? String(entry.id) : `mcp_manifests[${i}]`,
|
|
263830
|
+
manifest: m
|
|
263831
|
+
});
|
|
263832
|
+
}
|
|
263833
|
+
});
|
|
263834
|
+
}
|
|
263835
|
+
if (Array.isArray(lockDoc.mcp_servers)) {
|
|
263836
|
+
lockDoc.mcp_servers.forEach((entry, i) => {
|
|
263837
|
+
if (!entry) return;
|
|
263838
|
+
const m = normalizeMcpManifest(entry.manifest != null ? entry.manifest : entry);
|
|
263839
|
+
if (m) {
|
|
263840
|
+
out.push({
|
|
263841
|
+
id: (entry.id || entry.name) != null ? String(entry.id || entry.name) : `mcp_servers[${i}]`,
|
|
263842
|
+
manifest: m
|
|
263843
|
+
});
|
|
263844
|
+
}
|
|
263845
|
+
});
|
|
263846
|
+
}
|
|
263847
|
+
if (lockDoc.locked_mcp_manifest) {
|
|
263848
|
+
const m = normalizeMcpManifest(lockDoc.locked_mcp_manifest);
|
|
263849
|
+
if (m) out.push({ id: "locked_mcp_manifest", manifest: m });
|
|
263850
|
+
}
|
|
263851
|
+
return out;
|
|
263852
|
+
}
|
|
263853
|
+
function buildLockDrift(args = {}) {
|
|
263854
|
+
const serverId = args.serverId != null ? String(args.serverId) : "default";
|
|
263855
|
+
if (args.liveUnreachable === true) {
|
|
263856
|
+
return {
|
|
263857
|
+
status: "unreachable",
|
|
263858
|
+
drift: false,
|
|
263859
|
+
// honest: unknown, not drifted
|
|
263860
|
+
server_id: serverId,
|
|
263861
|
+
locked_hash: manifestContentHash(normalizeMcpManifest(args.lockedManifest)),
|
|
263862
|
+
live_hash: null,
|
|
263863
|
+
risk_score: null,
|
|
263864
|
+
level: null,
|
|
263865
|
+
signals: [],
|
|
263866
|
+
findings: [],
|
|
263867
|
+
summary: "Live MCP manifest unreachable \u2014 not scored as drift and not claimed unchanged (absence_is_unknown).",
|
|
263868
|
+
live_error: args.liveError != null ? String(args.liveError).slice(0, 300) : null,
|
|
263869
|
+
interpretation: "absence_is_unknown",
|
|
263870
|
+
note: "Drift is a scored finding of measurable change, not an accusation of intent. Unreachable \u2260 drifted. Monitor only \u2014 does not block.",
|
|
263871
|
+
scorer: "scoreMcpRisk"
|
|
263872
|
+
};
|
|
263873
|
+
}
|
|
263874
|
+
const locked = normalizeMcpManifest(args.lockedManifest);
|
|
263875
|
+
const live = normalizeMcpManifest(args.liveManifest);
|
|
263876
|
+
if (!locked) {
|
|
263877
|
+
return {
|
|
263878
|
+
status: "missing_locked",
|
|
263879
|
+
drift: false,
|
|
263880
|
+
server_id: serverId,
|
|
263881
|
+
locked_hash: null,
|
|
263882
|
+
live_hash: manifestContentHash(live),
|
|
263883
|
+
risk_score: null,
|
|
263884
|
+
level: null,
|
|
263885
|
+
signals: [],
|
|
263886
|
+
findings: [],
|
|
263887
|
+
summary: "No locked MCP manifest supplied \u2014 cannot compute drift (not a clean bill of health).",
|
|
263888
|
+
interpretation: "absence_is_unknown",
|
|
263889
|
+
note: "coderifts.lock v1 records observed agents/ops, not MCP tool bodies; pass --locked-manifest or additive lock mcp_manifests.",
|
|
263890
|
+
scorer: "scoreMcpRisk"
|
|
263891
|
+
};
|
|
263892
|
+
}
|
|
263893
|
+
if (!live) {
|
|
263894
|
+
return {
|
|
263895
|
+
status: "missing_live",
|
|
263896
|
+
drift: false,
|
|
263897
|
+
server_id: serverId,
|
|
263898
|
+
locked_hash: manifestContentHash(locked),
|
|
263899
|
+
live_hash: null,
|
|
263900
|
+
risk_score: null,
|
|
263901
|
+
level: null,
|
|
263902
|
+
signals: [],
|
|
263903
|
+
findings: [],
|
|
263904
|
+
summary: "No live MCP manifest supplied \u2014 cannot compute drift (absence_is_unknown).",
|
|
263905
|
+
interpretation: "absence_is_unknown",
|
|
263906
|
+
note: "Provide --live-manifest or --live-url. Unreachable fetches use status unreachable.",
|
|
263907
|
+
scorer: "scoreMcpRisk"
|
|
263908
|
+
};
|
|
263909
|
+
}
|
|
263910
|
+
const scored = scoreMcpRisk({
|
|
263911
|
+
oldManifest: locked,
|
|
263912
|
+
newManifest: live
|
|
263913
|
+
});
|
|
263914
|
+
const locked_hash = manifestContentHash(locked);
|
|
263915
|
+
const live_hash = manifestContentHash(live);
|
|
263916
|
+
const hashDiffers = locked_hash != null && live_hash != null && locked_hash !== live_hash;
|
|
263917
|
+
const scoredDrift = (scored.score || 0) > 0;
|
|
263918
|
+
const isDrift = hashDiffers || scoredDrift;
|
|
263919
|
+
const unclassified = hashDiffers && !scoredDrift;
|
|
263920
|
+
const findings = (scored.signals || []).filter((s) => s && (s.impact == null || Number(s.impact) > 0)).map((s) => ({
|
|
263921
|
+
signal: s.signal,
|
|
263922
|
+
impact: s.impact,
|
|
263923
|
+
detail: s.detail,
|
|
263924
|
+
data: s.data || null,
|
|
263925
|
+
// Honest framing: finding, not accusation
|
|
263926
|
+
kind: "measurable_change"
|
|
263927
|
+
}));
|
|
263928
|
+
findings.sort((a, b) => {
|
|
263929
|
+
const sa = String(a.signal || "");
|
|
263930
|
+
const sb = String(b.signal || "");
|
|
263931
|
+
if (sa !== sb) return sa < sb ? -1 : 1;
|
|
263932
|
+
return String(a.detail || "").localeCompare(String(b.detail || ""));
|
|
263933
|
+
});
|
|
263934
|
+
return {
|
|
263935
|
+
status: isDrift ? "drift" : "unchanged",
|
|
263936
|
+
drift: isDrift,
|
|
263937
|
+
server_id: serverId,
|
|
263938
|
+
locked_hash,
|
|
263939
|
+
live_hash,
|
|
263940
|
+
hash_differs: hashDiffers,
|
|
263941
|
+
risk_score: scored.score,
|
|
263942
|
+
level: scored.level,
|
|
263943
|
+
signals: scored.signals || [],
|
|
263944
|
+
findings,
|
|
263945
|
+
unclassified_change: unclassified,
|
|
263946
|
+
summary: unclassified ? "MCP manifest DIFFERS from the locked baseline (content hashes disagree), but no catalogued risk signal explains the difference. Treated as drift: an unrecognised change is still a change. Compare the manifests directly \u2014 the signal catalog is not exhaustive." : isDrift ? scored.summary || "MCP manifest differs from locked baseline (scored finding)." : "Live MCP manifest matches locked baseline under mcp-risk-scorer (no risk signals).",
|
|
263947
|
+
interpretation: "number_is_what_is",
|
|
263948
|
+
note: "Drift is a scored FINDING of measurable change (tool_removed, schema change, \u2026), not an accusation of poison vs legitimate update. Monitor only \u2014 does not block. Scoring: scoreMcpRisk (existing SIGNAL_CATALOG; no reimplemented weights).",
|
|
263949
|
+
scorer: "scoreMcpRisk",
|
|
263950
|
+
signal_catalog_version: "mcp-risk-scorer"
|
|
263951
|
+
};
|
|
263952
|
+
}
|
|
263953
|
+
function buildLockDriftReport(pairs) {
|
|
263954
|
+
const list = Array.isArray(pairs) ? pairs : [];
|
|
263955
|
+
const results = list.map((p, i) => buildLockDrift({
|
|
263956
|
+
...p,
|
|
263957
|
+
serverId: p.serverId != null ? p.serverId : `server_${i}`
|
|
263958
|
+
})).sort((a, b) => String(a.server_id).localeCompare(String(b.server_id)));
|
|
263959
|
+
const anyDrift = results.some((r) => r.drift === true);
|
|
263960
|
+
const anyUnreachable = results.some((r) => r.status === "unreachable");
|
|
263961
|
+
const maxScore = results.reduce((m, r) => {
|
|
263962
|
+
if (r.risk_score == null || !Number.isFinite(r.risk_score)) return m;
|
|
263963
|
+
return Math.max(m, r.risk_score);
|
|
263964
|
+
}, 0);
|
|
263965
|
+
return {
|
|
263966
|
+
status: anyUnreachable && !anyDrift ? results.every((r) => r.status === "unreachable") ? "unreachable" : "partial" : anyDrift ? "drift" : "unchanged",
|
|
263967
|
+
drift: anyDrift,
|
|
263968
|
+
max_risk_score: maxScore,
|
|
263969
|
+
servers: results,
|
|
263970
|
+
summary: anyDrift ? `MCP lock drift on ${results.filter((r) => r.drift).length}/${results.length} server(s); max risk ${maxScore}.` : anyUnreachable ? "No scored drift; one or more live manifests unreachable (absence_is_unknown)." : "No MCP lock drift detected under scoreMcpRisk.",
|
|
263971
|
+
interpretation: "number_is_what_is",
|
|
263972
|
+
note: "Monitor report only. Existing poison-gate / policy engine remain the blockers. Unreachable is never treated as clean or as drift."
|
|
263973
|
+
};
|
|
263974
|
+
}
|
|
263975
|
+
module2.exports = {
|
|
263976
|
+
buildLockDrift,
|
|
263977
|
+
buildLockDriftReport,
|
|
263978
|
+
normalizeMcpManifest,
|
|
263979
|
+
manifestContentHash,
|
|
263980
|
+
extractLockedManifestsFromLock,
|
|
263981
|
+
SIGNAL_CATALOG
|
|
263982
|
+
// re-export for tests asserting reuse
|
|
263983
|
+
};
|
|
263984
|
+
}
|
|
263985
|
+
});
|
|
263986
|
+
|
|
263447
263987
|
// src/commands/lock.js
|
|
263448
263988
|
var require_lock = __commonJS({
|
|
263449
263989
|
"src/commands/lock.js"(exports2, module2) {
|
|
@@ -263508,6 +264048,7 @@ var require_lock = __commonJS({
|
|
|
263508
264048
|
return `${JSON.stringify(doc, null, 2)}
|
|
263509
264049
|
`;
|
|
263510
264050
|
}
|
|
264051
|
+
var mcpLockDrift = require_mcp_lock_drift();
|
|
263511
264052
|
function loadDriftCore(deps = {}) {
|
|
263512
264053
|
if (deps.buildLockDrift) {
|
|
263513
264054
|
return {
|
|
@@ -263516,21 +264057,7 @@ var require_lock = __commonJS({
|
|
|
263516
264057
|
extractLockedManifestsFromLock: deps.extractLockedManifestsFromLock
|
|
263517
264058
|
};
|
|
263518
264059
|
}
|
|
263519
|
-
|
|
263520
|
-
path.join(__dirname, "../../../../src/core/mcp-lock-drift"),
|
|
263521
|
-
path.join(__dirname, "../../../src/core/mcp-lock-drift")
|
|
263522
|
-
];
|
|
263523
|
-
for (const c of candidates) {
|
|
263524
|
-
try {
|
|
263525
|
-
return require(c);
|
|
263526
|
-
} catch (_) {
|
|
263527
|
-
}
|
|
263528
|
-
}
|
|
263529
|
-
const err = new Error(
|
|
263530
|
-
"Cannot load mcp-lock-drift core. Run from the coderifts-app monorepo (packages/cli expects ../../../../src/core/mcp-lock-drift)."
|
|
263531
|
-
);
|
|
263532
|
-
err.code = "CORE_UNAVAILABLE";
|
|
263533
|
-
throw err;
|
|
264060
|
+
return mcpLockDrift;
|
|
263534
264061
|
}
|
|
263535
264062
|
function readJsonFile(filePath) {
|
|
263536
264063
|
const raw = fs.readFileSync(filePath, "utf8");
|
|
@@ -263804,6 +264331,176 @@ var require_lock = __commonJS({
|
|
|
263804
264331
|
}
|
|
263805
264332
|
});
|
|
263806
264333
|
|
|
264334
|
+
// ../../src/core/counterfactual-adopt.js
|
|
264335
|
+
var require_counterfactual_adopt = __commonJS({
|
|
264336
|
+
"../../src/core/counterfactual-adopt.js"(exports2, module2) {
|
|
264337
|
+
"use strict";
|
|
264338
|
+
var { analyzeSpecs } = require_analyzer();
|
|
264339
|
+
var SCOPE_NOTE = "COUNTERFACTUAL only: each row is what analyzeSpecs would have returned on the supplied before\u2192after content. It does not assert the change was actually harmful, and Step A does not join real decision_outcomes (outcome correlation is Step B).";
|
|
264340
|
+
function mapDecision(result) {
|
|
264341
|
+
if (!result || typeof result !== "object") return "REQUIRE_APPROVAL";
|
|
264342
|
+
const od = result.omega_decision;
|
|
264343
|
+
if (od === "ALLOW" || od === "WARN" || od === "REQUIRE_APPROVAL" || od === "BLOCK") {
|
|
264344
|
+
return od;
|
|
264345
|
+
}
|
|
264346
|
+
if (result.should_block === true) return "BLOCK";
|
|
264347
|
+
const pats = Array.isArray(result.detected_patterns) ? result.detected_patterns : [];
|
|
264348
|
+
if (pats.some((p) => p && (p.severity === "CRITICAL" || p.severity === "HIGH"))) {
|
|
264349
|
+
return "BLOCK";
|
|
264350
|
+
}
|
|
264351
|
+
const breaking = result.breaking_changes;
|
|
264352
|
+
const n = Array.isArray(breaking) ? breaking.length : typeof breaking === "number" ? breaking : 0;
|
|
264353
|
+
if (n > 0) return "REQUIRE_APPROVAL";
|
|
264354
|
+
return "ALLOW";
|
|
264355
|
+
}
|
|
264356
|
+
function mapExecutionAction(decision) {
|
|
264357
|
+
if (decision === "BLOCK") return "STOP";
|
|
264358
|
+
if (decision === "REQUIRE_APPROVAL") return "REQUEST_APPROVAL";
|
|
264359
|
+
if (decision === "WARN") return "CONTINUE_WITH_MONITORING";
|
|
264360
|
+
return "CONTINUE";
|
|
264361
|
+
}
|
|
264362
|
+
function extractDetectors(result) {
|
|
264363
|
+
const pats = Array.isArray(result && result.detected_patterns) ? result.detected_patterns : [];
|
|
264364
|
+
const out = [];
|
|
264365
|
+
for (const p of pats) {
|
|
264366
|
+
if (!p || typeof p !== "object") continue;
|
|
264367
|
+
const id = p.name || p.id || p.type;
|
|
264368
|
+
if (!id) continue;
|
|
264369
|
+
out.push({
|
|
264370
|
+
id: String(id),
|
|
264371
|
+
severity: p.severity != null ? String(p.severity) : "UNKNOWN"
|
|
264372
|
+
});
|
|
264373
|
+
}
|
|
264374
|
+
return out;
|
|
264375
|
+
}
|
|
264376
|
+
async function buildCounterfactualReport(changeSets, config = {}) {
|
|
264377
|
+
if (!Array.isArray(changeSets) || changeSets.length === 0) {
|
|
264378
|
+
return {
|
|
264379
|
+
change_sets: [],
|
|
264380
|
+
per_detector: [],
|
|
264381
|
+
scope_note: SCOPE_NOTE,
|
|
264382
|
+
interpretation: "absence_is_unknown",
|
|
264383
|
+
summary: {
|
|
264384
|
+
change_set_count: 0,
|
|
264385
|
+
would_stop: 0,
|
|
264386
|
+
would_request_approval: 0,
|
|
264387
|
+
would_continue: 0,
|
|
264388
|
+
note: 'No contract-artifact change sets supplied \u2014 empty is UNKNOWN, not "clean".'
|
|
264389
|
+
}
|
|
264390
|
+
};
|
|
264391
|
+
}
|
|
264392
|
+
const rows = [];
|
|
264393
|
+
const detectorMap = /* @__PURE__ */ new Map();
|
|
264394
|
+
for (const cs of changeSets) {
|
|
264395
|
+
const ref = cs && (cs.ref || cs.sha) || null;
|
|
264396
|
+
const date = cs && cs.date || null;
|
|
264397
|
+
const path = cs && cs.path || null;
|
|
264398
|
+
const before = cs && cs.before_spec != null ? String(cs.before_spec) : "";
|
|
264399
|
+
const after = cs && cs.after_spec != null ? String(cs.after_spec) : "";
|
|
264400
|
+
let result;
|
|
264401
|
+
let error = null;
|
|
264402
|
+
try {
|
|
264403
|
+
result = await analyzeSpecs(before, after, config || {});
|
|
264404
|
+
} catch (err) {
|
|
264405
|
+
error = {
|
|
264406
|
+
code: err && err.code ? String(err.code) : "analyze_error",
|
|
264407
|
+
message: err && err.message ? String(err.message).slice(0, 300) : "analyzeSpecs failed"
|
|
264408
|
+
};
|
|
264409
|
+
result = null;
|
|
264410
|
+
}
|
|
264411
|
+
if (!result) {
|
|
264412
|
+
rows.push({
|
|
264413
|
+
ref,
|
|
264414
|
+
date,
|
|
264415
|
+
path,
|
|
264416
|
+
decision: null,
|
|
264417
|
+
execution_action: null,
|
|
264418
|
+
risk_score: null,
|
|
264419
|
+
detectors_fired: [],
|
|
264420
|
+
would_have: null,
|
|
264421
|
+
error,
|
|
264422
|
+
counterfactual: true
|
|
264423
|
+
});
|
|
264424
|
+
continue;
|
|
264425
|
+
}
|
|
264426
|
+
const decision = mapDecision(result);
|
|
264427
|
+
const execution_action = mapExecutionAction(decision);
|
|
264428
|
+
const risk_score = result.risk_score != null && Number.isFinite(Number(result.risk_score)) ? Number(result.risk_score) : null;
|
|
264429
|
+
const detectors_fired = extractDetectors(result);
|
|
264430
|
+
for (const d of detectors_fired) {
|
|
264431
|
+
const prev = detectorMap.get(d.id);
|
|
264432
|
+
if (!prev) {
|
|
264433
|
+
detectorMap.set(d.id, { id: d.id, severity: d.severity, fire_count: 1 });
|
|
264434
|
+
} else {
|
|
264435
|
+
prev.fire_count += 1;
|
|
264436
|
+
prev.severity = worseSeverity(prev.severity, d.severity);
|
|
264437
|
+
}
|
|
264438
|
+
}
|
|
264439
|
+
rows.push({
|
|
264440
|
+
ref,
|
|
264441
|
+
date,
|
|
264442
|
+
path,
|
|
264443
|
+
decision,
|
|
264444
|
+
execution_action,
|
|
264445
|
+
risk_score,
|
|
264446
|
+
detectors_fired,
|
|
264447
|
+
would_have: {
|
|
264448
|
+
decision,
|
|
264449
|
+
execution_action,
|
|
264450
|
+
risk_score
|
|
264451
|
+
},
|
|
264452
|
+
error: null,
|
|
264453
|
+
counterfactual: true
|
|
264454
|
+
});
|
|
264455
|
+
}
|
|
264456
|
+
const per_detector = [...detectorMap.values()].sort((a, b) => b.fire_count - a.fire_count || a.id.localeCompare(b.id));
|
|
264457
|
+
let would_stop = 0;
|
|
264458
|
+
let would_request_approval = 0;
|
|
264459
|
+
let would_continue = 0;
|
|
264460
|
+
for (const r of rows) {
|
|
264461
|
+
if (r.execution_action === "STOP") would_stop += 1;
|
|
264462
|
+
else if (r.execution_action === "REQUEST_APPROVAL") would_request_approval += 1;
|
|
264463
|
+
else if (r.execution_action === "CONTINUE" || r.execution_action === "CONTINUE_WITH_MONITORING") {
|
|
264464
|
+
would_continue += 1;
|
|
264465
|
+
}
|
|
264466
|
+
}
|
|
264467
|
+
return {
|
|
264468
|
+
change_sets: rows,
|
|
264469
|
+
per_detector,
|
|
264470
|
+
scope_note: SCOPE_NOTE,
|
|
264471
|
+
interpretation: "counterfactual_observed",
|
|
264472
|
+
summary: {
|
|
264473
|
+
change_set_count: rows.length,
|
|
264474
|
+
would_stop,
|
|
264475
|
+
would_request_approval,
|
|
264476
|
+
would_continue,
|
|
264477
|
+
note: "Per-detector fire_count is the default-flip evidence input: high-volume detectors are false-positive noise candidates; rare+severe detectors are safer default-block candidates. This report does NOT auto-recommend a flip set."
|
|
264478
|
+
}
|
|
264479
|
+
};
|
|
264480
|
+
}
|
|
264481
|
+
var SEV_RANK = {
|
|
264482
|
+
CRITICAL: 4,
|
|
264483
|
+
HIGH: 3,
|
|
264484
|
+
MEDIUM: 2,
|
|
264485
|
+
LOW: 1,
|
|
264486
|
+
INFO: 0,
|
|
264487
|
+
UNKNOWN: 0
|
|
264488
|
+
};
|
|
264489
|
+
function worseSeverity(a, b) {
|
|
264490
|
+
const ra = SEV_RANK[String(a).toUpperCase()] != null ? SEV_RANK[String(a).toUpperCase()] : 0;
|
|
264491
|
+
const rb = SEV_RANK[String(b).toUpperCase()] != null ? SEV_RANK[String(b).toUpperCase()] : 0;
|
|
264492
|
+
return rb > ra ? b : a;
|
|
264493
|
+
}
|
|
264494
|
+
module2.exports = {
|
|
264495
|
+
buildCounterfactualReport,
|
|
264496
|
+
mapDecision,
|
|
264497
|
+
mapExecutionAction,
|
|
264498
|
+
extractDetectors,
|
|
264499
|
+
SCOPE_NOTE
|
|
264500
|
+
};
|
|
264501
|
+
}
|
|
264502
|
+
});
|
|
264503
|
+
|
|
263807
264504
|
// src/commands/adopt.js
|
|
263808
264505
|
var require_adopt = __commonJS({
|
|
263809
264506
|
"src/commands/adopt.js"(exports2, module2) {
|
|
@@ -263868,20 +264565,9 @@ var require_adopt = __commonJS({
|
|
|
263868
264565
|
else process.env.LOG_LEVEL = prevEnv;
|
|
263869
264566
|
}
|
|
263870
264567
|
}
|
|
264568
|
+
var counterfactualAdopt = require_counterfactual_adopt();
|
|
263871
264569
|
function loadCore() {
|
|
263872
|
-
|
|
263873
|
-
path.join(__dirname, "../../../../src/core/counterfactual-adopt"),
|
|
263874
|
-
path.join(__dirname, "../../../src/core/counterfactual-adopt")
|
|
263875
|
-
];
|
|
263876
|
-
for (const c of candidates) {
|
|
263877
|
-
try {
|
|
263878
|
-
return require(c);
|
|
263879
|
-
} catch (_) {
|
|
263880
|
-
}
|
|
263881
|
-
}
|
|
263882
|
-
throw new Error(
|
|
263883
|
-
"Cannot load counterfactual-adopt core. Run from the coderifts-app monorepo (packages/cli expects ../../../../src/core/counterfactual-adopt)."
|
|
263884
|
-
);
|
|
264570
|
+
return counterfactualAdopt;
|
|
263885
264571
|
}
|
|
263886
264572
|
function listCommits({ commits, days, gitImpl, cwd }) {
|
|
263887
264573
|
const args = ["log", "--format=%H %cI", "--reverse"];
|