@fidacy/mcp 0.6.1 → 0.6.2
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/dist/core.js +1 -1
- package/dist/index.js +69 -20
- package/dist/lib.js +44 -15
- package/dist/postinstall.js +1 -1
- package/dist/reporting.d.ts +22 -0
- package/package.json +13 -12
package/dist/core.js
CHANGED
|
@@ -488,7 +488,7 @@ function resolveMandateRules(cfg) {
|
|
|
488
488
|
}
|
|
489
489
|
|
|
490
490
|
// src/telemetry.ts
|
|
491
|
-
var CLIENT_VERSION = true ? "0.6.
|
|
491
|
+
var CLIENT_VERSION = true ? "0.6.2" : "dev";
|
|
492
492
|
function bandOf(amount) {
|
|
493
493
|
if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
|
|
494
494
|
if (amount < 10) return "lt10";
|
package/dist/index.js
CHANGED
|
@@ -214,7 +214,7 @@ var init_telemetry = __esm({
|
|
|
214
214
|
"src/telemetry.ts"() {
|
|
215
215
|
"use strict";
|
|
216
216
|
init_config();
|
|
217
|
-
CLIENT_VERSION = true ? "0.6.
|
|
217
|
+
CLIENT_VERSION = true ? "0.6.2" : "dev";
|
|
218
218
|
currentShell = "mcp";
|
|
219
219
|
anonIdCache = null;
|
|
220
220
|
buffer = [];
|
|
@@ -291,7 +291,7 @@ var init_register = __esm({
|
|
|
291
291
|
"src/register.ts"() {
|
|
292
292
|
"use strict";
|
|
293
293
|
init_config();
|
|
294
|
-
CLIENT_VERSION3 = true ? "0.6.
|
|
294
|
+
CLIENT_VERSION3 = true ? "0.6.2" : "dev";
|
|
295
295
|
}
|
|
296
296
|
});
|
|
297
297
|
|
|
@@ -1267,7 +1267,7 @@ init_telemetry();
|
|
|
1267
1267
|
|
|
1268
1268
|
// src/provision.ts
|
|
1269
1269
|
init_config();
|
|
1270
|
-
var CLIENT_VERSION2 = true ? "0.6.
|
|
1270
|
+
var CLIENT_VERSION2 = true ? "0.6.2" : "dev";
|
|
1271
1271
|
function provisionEnabled() {
|
|
1272
1272
|
const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
|
|
1273
1273
|
return !(v === "1" || v === "true" || v === "yes");
|
|
@@ -1384,7 +1384,7 @@ function renderSummary(s) {
|
|
|
1384
1384
|
];
|
|
1385
1385
|
if (s.denied > 0) {
|
|
1386
1386
|
const ranked = Object.entries(s.denied_by_rule).sort((a, b) => b[1] - a[1]);
|
|
1387
|
-
lines.push(`Blocked: ${money(s.blocked_totals)} in attempted value. By
|
|
1387
|
+
lines.push(`Blocked: ${money(s.blocked_totals)} in attempted value. By reason: ${ranked.map(([rule, n]) => `${ruleLabel(rule)} (${n})`).join(", ")}.`);
|
|
1388
1388
|
}
|
|
1389
1389
|
if (s.amount_unknown > 0) {
|
|
1390
1390
|
lines.push(`${s.amount_unknown} record(s) in this window predate amount logging, so the totals above are a floor, not the exact figure.`);
|
|
@@ -1411,32 +1411,82 @@ function renderRows(rows2) {
|
|
|
1411
1411
|
return rows2.map((r) => {
|
|
1412
1412
|
const amount = typeof r.amount === "number" ? `${r.amount.toLocaleString("en-US")} ${r.currency ?? ""}`.trim() : "amount not recorded";
|
|
1413
1413
|
const who = r.payee ?? "payee not recorded";
|
|
1414
|
-
const why = r.status === "DENY" ? `
|
|
1414
|
+
const why = r.status === "DENY" ? ` ${explainRule(r.violatedRule, r.payee)}` : "";
|
|
1415
1415
|
const said = r.purpose ? ` Agent's stated reason: "${r.purpose}".` : "";
|
|
1416
1416
|
return `#${r.seq} ${r.ts} ${r.status} ${amount} to ${who}.${why}${said} (decision ${r.decisionId})`;
|
|
1417
1417
|
}).join("\n");
|
|
1418
1418
|
}
|
|
1419
|
+
function ruleLabel(rule) {
|
|
1420
|
+
if (rule.startsWith("payee_lookalike")) return "lookalike payee";
|
|
1421
|
+
if (rule.startsWith("payee_not_in_allowlist")) return "payee not approved";
|
|
1422
|
+
if (rule.startsWith("category_not_allowed")) return "purpose not allowed";
|
|
1423
|
+
if (rule.startsWith("currency_not_allowed")) return "wrong currency";
|
|
1424
|
+
if (rule.startsWith("per_tx") || rule.includes("perTxMax")) return "over the per-payment cap";
|
|
1425
|
+
if (rule.includes("maxTotal") || rule.startsWith("total_cap") || rule.startsWith("budget")) return "over the total budget";
|
|
1426
|
+
if (rule.startsWith("invoice") || rule.includes("duplicate")) return "duplicate invoice";
|
|
1427
|
+
if (rule.startsWith("window") || rule.includes("mandate_window") || rule.includes("expired")) return "outside the mandate window";
|
|
1428
|
+
if (rule.startsWith("revoked") || rule.startsWith("mandate_revoked")) return "mandate revoked";
|
|
1429
|
+
if (rule.startsWith("invalid_mandate")) return "mandate misconfigured";
|
|
1430
|
+
if (rule.startsWith("non_positive_amount")) return "invalid amount";
|
|
1431
|
+
if (rule.startsWith("activation_required")) return "trial used up";
|
|
1432
|
+
if (rule === "not_recorded") return "rule not recorded";
|
|
1433
|
+
return rule;
|
|
1434
|
+
}
|
|
1419
1435
|
function explainRule(rule, payee) {
|
|
1420
1436
|
if (!rule) return "This entry predates readable rule logging, so the specific rule is not on the record. The decision payload is still covered by the chain digest.";
|
|
1437
|
+
if (rule.startsWith("invalid_mandate")) {
|
|
1438
|
+
const cap = rule.includes("perTxMax") ? "perTxMax" : rule.includes("maxTotal") ? "maxTotal" : null;
|
|
1439
|
+
if (cap)
|
|
1440
|
+
return `The mandate's \`${cap}\` is not a usable number, so the firewall denied rather than run without that cap. Values like "2,500", "$2500" or "2500 USD" all look right to a human and become NaN, and a cap that cannot be compared would silently let everything through. Write it as a bare number: ${cap}: 2500.`;
|
|
1441
|
+
return "The mandate is missing its `allow` block, so there is nothing to enforce and the firewall denied. Check ~/.fidacy/config.json or FIDACY_MANDATE_JSON.";
|
|
1442
|
+
}
|
|
1443
|
+
if (rule.startsWith("payee_lookalike")) {
|
|
1444
|
+
const approved = rule.includes("~") ? rule.split("~")[1] : "an approved payee";
|
|
1445
|
+
return `The payee${payee ? ` "${payee}"` : ""} is a lookalike of your approved payee "${approved}": same name to a human glance, different string. That is the signature of a payee swap, whether from a prompt injection or a spoofed invoice. If it really is a separate vendor, add it to \`payees\` explicitly; if it is not, this attempt is exactly what the firewall exists to stop.`;
|
|
1446
|
+
}
|
|
1421
1447
|
if (rule.startsWith("payee_not_in_allowlist"))
|
|
1422
|
-
return `The payee${payee ? ` "${payee}"` : ""} is not on the mandate's allowlist. This is the deny-by-default rule doing its job: an agent can only pay counterparties a human put on the list, which is what stops a swapped or lookalike vendor.`;
|
|
1448
|
+
return `The payee${payee ? ` "${payee}"` : ""} is not on the mandate's allowlist. This is the deny-by-default rule doing its job: an agent can only pay counterparties a human put on the list, which is what stops a swapped or lookalike vendor. To authorize this one, add it to \`payees\` in ~/.fidacy/config.json, or pass a full mandate through FIDACY_MANDATE_JSON.`;
|
|
1423
1449
|
if (rule.startsWith("category_not_allowed"))
|
|
1424
|
-
return "The stated purpose category is not one the mandate permits for this agent.";
|
|
1450
|
+
return "The stated purpose category is not one the mandate permits for this agent. Add it to `categories` in the mandate, or send the payment under a purpose the mandate already allows.";
|
|
1425
1451
|
if (rule.startsWith("currency_not_allowed"))
|
|
1426
|
-
return "The payment currency is not the mandate's currency.";
|
|
1452
|
+
return "The payment currency is not the mandate's currency. Set `currency` to the one you intend to pay in. A mandate authorizes one currency at a time on purpose, so a swapped currency cannot slip past a cap set in another.";
|
|
1427
1453
|
if (rule.startsWith("per_tx") || rule.includes("perTxMax"))
|
|
1428
|
-
return "The amount exceeds the per-transaction ceiling in the mandate.";
|
|
1429
|
-
if (rule.includes("maxTotal") || rule.startsWith("budget"))
|
|
1430
|
-
return "The payment would push cumulative spend past the mandate's total budget for the window.";
|
|
1454
|
+
return "The amount exceeds the per-transaction ceiling in the mandate. Raise `perTxMax` if payments this size are expected, or split the payment.";
|
|
1455
|
+
if (rule.includes("maxTotal") || rule.startsWith("budget") || rule.startsWith("total_cap"))
|
|
1456
|
+
return "The payment would push cumulative spend past the mandate's total budget for the window. Raise `maxTotal`, or wait for the window to roll over. Blocked attempts do not consume the budget; only payments that went through do.";
|
|
1431
1457
|
if (rule.startsWith("invoice") || rule.includes("duplicate"))
|
|
1432
|
-
return "This invoice was already paid once. One payment per invoice is enforced regardless of amount, which is the control that stops a re-presented invoice at a higher figure.";
|
|
1433
|
-
if (rule.startsWith("window") || rule.includes("expired"))
|
|
1434
|
-
return "The mandate was outside its validity window at the moment of the request.";
|
|
1435
|
-
if (rule.startsWith("revoked")
|
|
1458
|
+
return "This invoice was already paid once. One payment per invoice is enforced regardless of amount, which is the control that stops a re-presented invoice at a higher figure. If this genuinely is a second, separate payment, give it its own invoiceRef.";
|
|
1459
|
+
if (rule.startsWith("window") || rule.includes("mandate_window") || rule.includes("expired"))
|
|
1460
|
+
return "The mandate was outside its validity window at the moment of the request. Check `notBefore` and `notAfter`, and issue a mandate whose window covers now.";
|
|
1461
|
+
if (rule.startsWith("revoked") || rule.startsWith("mandate_revoked"))
|
|
1462
|
+
return "The mandate had been revoked. A revoked mandate cannot be un-revoked; issue a new one to authorize further payments.";
|
|
1463
|
+
if (rule.startsWith("non_positive_amount"))
|
|
1464
|
+
return "The amount was zero or negative, which is not a payment anyone can authorize. Check what produced that value before retrying.";
|
|
1436
1465
|
if (rule.startsWith("activation_required"))
|
|
1437
1466
|
return "The anonymous trial had been used up, so the firewall failed closed. No money can move until the install is activated with a free API key.";
|
|
1438
1467
|
return `The mandate rule "${rule}" was violated.`;
|
|
1439
1468
|
}
|
|
1469
|
+
function explainEngineError(action, status, type, reasons) {
|
|
1470
|
+
const noun = action === "verdict" ? "assess this action" : action === "anchor" ? "anchor this artifact" : "check this artifact";
|
|
1471
|
+
if (status === 401 || status === 403)
|
|
1472
|
+
return `Fidacy could not ${noun}: the engine rejected the credential (HTTP ${status}). This is not an outage. The key is missing, wrong, or lacks the scope this call needs \u2014 an anonymous provisioned key has no scopes at all. Get an account key at https://fidacy.com/claim and set FIDACY_ENGINE_API_KEY. Nothing was executed.`;
|
|
1473
|
+
if (status === 402)
|
|
1474
|
+
return `Fidacy could not ${noun}: the account is past a spending or quota limit (HTTP 402). The integration is fine; the limit needs settling at https://fidacy.com/claim. Nothing was executed.`;
|
|
1475
|
+
if (status === 429)
|
|
1476
|
+
return `Fidacy could not ${noun}: too many requests in a short window (HTTP 429). Retry in a few seconds. Nothing was executed, and no decision was recorded.`;
|
|
1477
|
+
if (status === 404 && action === "check")
|
|
1478
|
+
return "No anchor exists for that hash on this account. Either the artifact was never anchored, or its contents changed since it was \u2014 if you expected a match, the file is not the one that was anchored.";
|
|
1479
|
+
if (status === 422 || type === "mandate_violation") {
|
|
1480
|
+
const first = reasons?.[0]?.key;
|
|
1481
|
+
const why = first ? ` ${explainRule(first)}` : "";
|
|
1482
|
+
return `Fidacy denied this action: it falls outside the mandate in force.${why} Nothing was executed.`;
|
|
1483
|
+
}
|
|
1484
|
+
if (status >= 500)
|
|
1485
|
+
return `Fidacy could not ${noun}: the engine returned an error (HTTP ${status}). Nothing was executed, and the call was refused rather than allowed through. Retrying is safe.`;
|
|
1486
|
+
if (status === 400)
|
|
1487
|
+
return `Fidacy could not ${noun}: the request was malformed (HTTP 400${type ? `, ${type}` : ""}). This is a bug in how the call was built, not a policy decision. Nothing was executed.`;
|
|
1488
|
+
return `Fidacy could not ${noun} (HTTP ${status}${type ? `, ${type}` : ""}), so the call was refused rather than allowed through. Nothing was executed.`;
|
|
1489
|
+
}
|
|
1440
1490
|
|
|
1441
1491
|
// src/banner.ts
|
|
1442
1492
|
init_activation();
|
|
@@ -1594,7 +1644,7 @@ function renderAlertLine(alerts) {
|
|
|
1594
1644
|
var state = ensureState();
|
|
1595
1645
|
var core = makeCore();
|
|
1596
1646
|
var subject = process.env.FIDACY_SUBJECT ?? "agent:demo";
|
|
1597
|
-
var SERVER_VERSION = true ? "0.6.
|
|
1647
|
+
var SERVER_VERSION = true ? "0.6.2" : "dev";
|
|
1598
1648
|
var bootClaim = claimUrl();
|
|
1599
1649
|
var server = new McpServer(
|
|
1600
1650
|
// The commercial identity. `name` is the stable slug hosts key on; `title` is
|
|
@@ -1859,9 +1909,8 @@ server.registerTool(
|
|
|
1859
1909
|
markHostedLapsed();
|
|
1860
1910
|
return { content: [{ type: "text", text: hostedWallCta("verdict", e.billing) }], isError: true };
|
|
1861
1911
|
}
|
|
1862
|
-
const reasons = e.rejection_reasons?.length ? " (" + e.rejection_reasons.map((x) => x.key).join(",") + ")" : "";
|
|
1863
1912
|
return {
|
|
1864
|
-
content: [{ type: "text", text:
|
|
1913
|
+
content: [{ type: "text", text: explainEngineError("verdict", e.status, e.type, e.rejection_reasons) }],
|
|
1865
1914
|
isError: true
|
|
1866
1915
|
};
|
|
1867
1916
|
}
|
|
@@ -1925,7 +1974,7 @@ server.registerTool(
|
|
|
1925
1974
|
markHostedLapsed();
|
|
1926
1975
|
return { content: [{ type: "text", text: hostedWallCta("anchor", e.billing) }], isError: true };
|
|
1927
1976
|
}
|
|
1928
|
-
return { content: [{ type: "text", text:
|
|
1977
|
+
return { content: [{ type: "text", text: explainEngineError("anchor", e.status, e.type) }], isError: true };
|
|
1929
1978
|
}
|
|
1930
1979
|
return { content: [{ type: "text", text: "ANCHOR failed: unexpected error" }], isError: true };
|
|
1931
1980
|
}
|
|
@@ -1978,7 +2027,7 @@ server.registerTool(
|
|
|
1978
2027
|
};
|
|
1979
2028
|
} catch (e) {
|
|
1980
2029
|
if (e instanceof AssessError) {
|
|
1981
|
-
return { content: [{ type: "text", text:
|
|
2030
|
+
return { content: [{ type: "text", text: explainEngineError("check", e.status, e.type) }], isError: true };
|
|
1982
2031
|
}
|
|
1983
2032
|
return { content: [{ type: "text", text: "CHECK failed: unexpected error" }], isError: true };
|
|
1984
2033
|
}
|
package/dist/lib.js
CHANGED
|
@@ -643,7 +643,7 @@ function resolveMandateRules(cfg) {
|
|
|
643
643
|
}
|
|
644
644
|
|
|
645
645
|
// src/telemetry.ts
|
|
646
|
-
var CLIENT_VERSION = true ? "0.6.
|
|
646
|
+
var CLIENT_VERSION = true ? "0.6.2" : "dev";
|
|
647
647
|
function bandOf(amount) {
|
|
648
648
|
if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
|
|
649
649
|
if (amount < 10) return "lt10";
|
|
@@ -831,7 +831,7 @@ function requestUpgrade() {
|
|
|
831
831
|
}
|
|
832
832
|
|
|
833
833
|
// src/provision.ts
|
|
834
|
-
var CLIENT_VERSION2 = true ? "0.6.
|
|
834
|
+
var CLIENT_VERSION2 = true ? "0.6.2" : "dev";
|
|
835
835
|
function provisionEnabled() {
|
|
836
836
|
const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
|
|
837
837
|
return !(v === "1" || v === "true" || v === "yes");
|
|
@@ -994,7 +994,7 @@ function activationBootLine() {
|
|
|
994
994
|
}
|
|
995
995
|
|
|
996
996
|
// src/register.ts
|
|
997
|
-
var CLIENT_VERSION3 = true ? "0.6.
|
|
997
|
+
var CLIENT_VERSION3 = true ? "0.6.2" : "dev";
|
|
998
998
|
function endpoint3() {
|
|
999
999
|
const base = (process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com").replace(/\/$/, "");
|
|
1000
1000
|
return `${base}/v1/register`;
|
|
@@ -1160,7 +1160,7 @@ function renderSummary(s) {
|
|
|
1160
1160
|
];
|
|
1161
1161
|
if (s.denied > 0) {
|
|
1162
1162
|
const ranked = Object.entries(s.denied_by_rule).sort((a, b) => b[1] - a[1]);
|
|
1163
|
-
lines.push(`Blocked: ${money(s.blocked_totals)} in attempted value. By
|
|
1163
|
+
lines.push(`Blocked: ${money(s.blocked_totals)} in attempted value. By reason: ${ranked.map(([rule, n]) => `${ruleLabel(rule)} (${n})`).join(", ")}.`);
|
|
1164
1164
|
}
|
|
1165
1165
|
if (s.amount_unknown > 0) {
|
|
1166
1166
|
lines.push(`${s.amount_unknown} record(s) in this window predate amount logging, so the totals above are a floor, not the exact figure.`);
|
|
@@ -1187,28 +1187,57 @@ function renderRows(rows) {
|
|
|
1187
1187
|
return rows.map((r) => {
|
|
1188
1188
|
const amount = typeof r.amount === "number" ? `${r.amount.toLocaleString("en-US")} ${r.currency ?? ""}`.trim() : "amount not recorded";
|
|
1189
1189
|
const who = r.payee ?? "payee not recorded";
|
|
1190
|
-
const why = r.status === "DENY" ? `
|
|
1190
|
+
const why = r.status === "DENY" ? ` ${explainRule(r.violatedRule, r.payee)}` : "";
|
|
1191
1191
|
const said = r.purpose ? ` Agent's stated reason: "${r.purpose}".` : "";
|
|
1192
1192
|
return `#${r.seq} ${r.ts} ${r.status} ${amount} to ${who}.${why}${said} (decision ${r.decisionId})`;
|
|
1193
1193
|
}).join("\n");
|
|
1194
1194
|
}
|
|
1195
|
+
function ruleLabel(rule) {
|
|
1196
|
+
if (rule.startsWith("payee_lookalike")) return "lookalike payee";
|
|
1197
|
+
if (rule.startsWith("payee_not_in_allowlist")) return "payee not approved";
|
|
1198
|
+
if (rule.startsWith("category_not_allowed")) return "purpose not allowed";
|
|
1199
|
+
if (rule.startsWith("currency_not_allowed")) return "wrong currency";
|
|
1200
|
+
if (rule.startsWith("per_tx") || rule.includes("perTxMax")) return "over the per-payment cap";
|
|
1201
|
+
if (rule.includes("maxTotal") || rule.startsWith("total_cap") || rule.startsWith("budget")) return "over the total budget";
|
|
1202
|
+
if (rule.startsWith("invoice") || rule.includes("duplicate")) return "duplicate invoice";
|
|
1203
|
+
if (rule.startsWith("window") || rule.includes("mandate_window") || rule.includes("expired")) return "outside the mandate window";
|
|
1204
|
+
if (rule.startsWith("revoked") || rule.startsWith("mandate_revoked")) return "mandate revoked";
|
|
1205
|
+
if (rule.startsWith("invalid_mandate")) return "mandate misconfigured";
|
|
1206
|
+
if (rule.startsWith("non_positive_amount")) return "invalid amount";
|
|
1207
|
+
if (rule.startsWith("activation_required")) return "trial used up";
|
|
1208
|
+
if (rule === "not_recorded") return "rule not recorded";
|
|
1209
|
+
return rule;
|
|
1210
|
+
}
|
|
1195
1211
|
function explainRule(rule, payee) {
|
|
1196
1212
|
if (!rule) return "This entry predates readable rule logging, so the specific rule is not on the record. The decision payload is still covered by the chain digest.";
|
|
1213
|
+
if (rule.startsWith("invalid_mandate")) {
|
|
1214
|
+
const cap = rule.includes("perTxMax") ? "perTxMax" : rule.includes("maxTotal") ? "maxTotal" : null;
|
|
1215
|
+
if (cap)
|
|
1216
|
+
return `The mandate's \`${cap}\` is not a usable number, so the firewall denied rather than run without that cap. Values like "2,500", "$2500" or "2500 USD" all look right to a human and become NaN, and a cap that cannot be compared would silently let everything through. Write it as a bare number: ${cap}: 2500.`;
|
|
1217
|
+
return "The mandate is missing its `allow` block, so there is nothing to enforce and the firewall denied. Check ~/.fidacy/config.json or FIDACY_MANDATE_JSON.";
|
|
1218
|
+
}
|
|
1219
|
+
if (rule.startsWith("payee_lookalike")) {
|
|
1220
|
+
const approved = rule.includes("~") ? rule.split("~")[1] : "an approved payee";
|
|
1221
|
+
return `The payee${payee ? ` "${payee}"` : ""} is a lookalike of your approved payee "${approved}": same name to a human glance, different string. That is the signature of a payee swap, whether from a prompt injection or a spoofed invoice. If it really is a separate vendor, add it to \`payees\` explicitly; if it is not, this attempt is exactly what the firewall exists to stop.`;
|
|
1222
|
+
}
|
|
1197
1223
|
if (rule.startsWith("payee_not_in_allowlist"))
|
|
1198
|
-
return `The payee${payee ? ` "${payee}"` : ""} is not on the mandate's allowlist. This is the deny-by-default rule doing its job: an agent can only pay counterparties a human put on the list, which is what stops a swapped or lookalike vendor.`;
|
|
1224
|
+
return `The payee${payee ? ` "${payee}"` : ""} is not on the mandate's allowlist. This is the deny-by-default rule doing its job: an agent can only pay counterparties a human put on the list, which is what stops a swapped or lookalike vendor. To authorize this one, add it to \`payees\` in ~/.fidacy/config.json, or pass a full mandate through FIDACY_MANDATE_JSON.`;
|
|
1199
1225
|
if (rule.startsWith("category_not_allowed"))
|
|
1200
|
-
return "The stated purpose category is not one the mandate permits for this agent.";
|
|
1226
|
+
return "The stated purpose category is not one the mandate permits for this agent. Add it to `categories` in the mandate, or send the payment under a purpose the mandate already allows.";
|
|
1201
1227
|
if (rule.startsWith("currency_not_allowed"))
|
|
1202
|
-
return "The payment currency is not the mandate's currency.";
|
|
1228
|
+
return "The payment currency is not the mandate's currency. Set `currency` to the one you intend to pay in. A mandate authorizes one currency at a time on purpose, so a swapped currency cannot slip past a cap set in another.";
|
|
1203
1229
|
if (rule.startsWith("per_tx") || rule.includes("perTxMax"))
|
|
1204
|
-
return "The amount exceeds the per-transaction ceiling in the mandate.";
|
|
1205
|
-
if (rule.includes("maxTotal") || rule.startsWith("budget"))
|
|
1206
|
-
return "The payment would push cumulative spend past the mandate's total budget for the window.";
|
|
1230
|
+
return "The amount exceeds the per-transaction ceiling in the mandate. Raise `perTxMax` if payments this size are expected, or split the payment.";
|
|
1231
|
+
if (rule.includes("maxTotal") || rule.startsWith("budget") || rule.startsWith("total_cap"))
|
|
1232
|
+
return "The payment would push cumulative spend past the mandate's total budget for the window. Raise `maxTotal`, or wait for the window to roll over. Blocked attempts do not consume the budget; only payments that went through do.";
|
|
1207
1233
|
if (rule.startsWith("invoice") || rule.includes("duplicate"))
|
|
1208
|
-
return "This invoice was already paid once. One payment per invoice is enforced regardless of amount, which is the control that stops a re-presented invoice at a higher figure.";
|
|
1209
|
-
if (rule.startsWith("window") || rule.includes("expired"))
|
|
1210
|
-
return "The mandate was outside its validity window at the moment of the request.";
|
|
1211
|
-
if (rule.startsWith("revoked")
|
|
1234
|
+
return "This invoice was already paid once. One payment per invoice is enforced regardless of amount, which is the control that stops a re-presented invoice at a higher figure. If this genuinely is a second, separate payment, give it its own invoiceRef.";
|
|
1235
|
+
if (rule.startsWith("window") || rule.includes("mandate_window") || rule.includes("expired"))
|
|
1236
|
+
return "The mandate was outside its validity window at the moment of the request. Check `notBefore` and `notAfter`, and issue a mandate whose window covers now.";
|
|
1237
|
+
if (rule.startsWith("revoked") || rule.startsWith("mandate_revoked"))
|
|
1238
|
+
return "The mandate had been revoked. A revoked mandate cannot be un-revoked; issue a new one to authorize further payments.";
|
|
1239
|
+
if (rule.startsWith("non_positive_amount"))
|
|
1240
|
+
return "The amount was zero or negative, which is not a payment anyone can authorize. Check what produced that value before retrying.";
|
|
1212
1241
|
if (rule.startsWith("activation_required"))
|
|
1213
1242
|
return "The anonymous trial had been used up, so the firewall failed closed. No money can move until the install is activated with a free API key.";
|
|
1214
1243
|
return `The mandate rule "${rule}" was violated.`;
|
package/dist/postinstall.js
CHANGED
package/dist/reporting.d.ts
CHANGED
|
@@ -56,4 +56,26 @@ export declare function renderRows(rows: DecisionRow[]): string;
|
|
|
56
56
|
* "why" without reading a rule name: a violated rule is a policy sentence, and
|
|
57
57
|
* the mandate that produced it is the thing they can actually change.
|
|
58
58
|
*/
|
|
59
|
+
/**
|
|
60
|
+
* Two-to-four word name for a rule, for places that RANK rules rather than
|
|
61
|
+
* explain one. The ranked list in a summary needs a label a human can scan, not
|
|
62
|
+
* a paragraph and not `per_tx_cap_exceeded:4200>1000`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function ruleLabel(rule: string): string;
|
|
59
65
|
export declare function explainRule(rule: string | undefined, payee?: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* Plain-language reading of an ENGINE failure, for the moment a call is refused.
|
|
68
|
+
*
|
|
69
|
+
* The hosted path used to surface `ASSESS 422: mandate_violation (payee_not_in_allowlist)`
|
|
70
|
+
* verbatim. That is a log line: it names a status code and an internal key, and
|
|
71
|
+
* leaves the operator to guess whether Fidacy is down, their key is wrong, or
|
|
72
|
+
* their own policy just did its job. Those three have completely different fixes,
|
|
73
|
+
* and only one of them is a problem with Fidacy.
|
|
74
|
+
*
|
|
75
|
+
* `reasons` are the engine's rejection keys, which map onto the same vocabulary
|
|
76
|
+
* explainRule already speaks, so a policy rejection reads the same whether it was
|
|
77
|
+
* decided locally or hosted.
|
|
78
|
+
*/
|
|
79
|
+
export declare function explainEngineError(action: "verdict" | "anchor" | "check", status: number, type: string, reasons?: {
|
|
80
|
+
key: string;
|
|
81
|
+
}[]): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fidacy/mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Fidacy action firewall for AI agents. Mandate-gated payment authorization as an MCP server.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Fidacy (ZeepCode Group Technology LLC) <hello@fidacy.com> (https://fidacy.com)",
|
|
@@ -50,6 +50,15 @@
|
|
|
50
50
|
"publishConfig": {
|
|
51
51
|
"access": "public"
|
|
52
52
|
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "node scripts/bundle.mjs",
|
|
55
|
+
"dev": "tsx watch src/index.ts",
|
|
56
|
+
"start": "node dist/index.js",
|
|
57
|
+
"prepublishOnly": "npm run build",
|
|
58
|
+
"typecheck": "tsc --noEmit",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"postinstall": "node -e \"try{require('fs').accessSync('dist/postinstall.js')}catch{process.exit(0)};import('./dist/postinstall.js')\" || exit 0"
|
|
61
|
+
},
|
|
53
62
|
"engines": {
|
|
54
63
|
"node": ">=18"
|
|
55
64
|
},
|
|
@@ -58,23 +67,15 @@
|
|
|
58
67
|
"zod": "^3.25.0"
|
|
59
68
|
},
|
|
60
69
|
"devDependencies": {
|
|
70
|
+
"@fidacy/firewall": "workspace:*",
|
|
61
71
|
"@types/node": "^22.10.0",
|
|
62
72
|
"tsx": "^4.19.2",
|
|
63
73
|
"typescript": "^5.7.2",
|
|
64
|
-
"vitest": "^3.2.4"
|
|
65
|
-
"@fidacy/firewall": "0.1.1"
|
|
74
|
+
"vitest": "^3.2.4"
|
|
66
75
|
},
|
|
67
76
|
"mcpName": "com.fidacy/ai-agent-firewall",
|
|
68
77
|
"repository": {
|
|
69
78
|
"type": "git",
|
|
70
79
|
"url": "git+https://github.com/lucaslubi/fidacy-mcp.git"
|
|
71
|
-
},
|
|
72
|
-
"scripts": {
|
|
73
|
-
"build": "node scripts/bundle.mjs",
|
|
74
|
-
"dev": "tsx watch src/index.ts",
|
|
75
|
-
"start": "node dist/index.js",
|
|
76
|
-
"typecheck": "tsc --noEmit",
|
|
77
|
-
"test": "vitest run",
|
|
78
|
-
"postinstall": "node -e \"try{require('fs').accessSync('dist/postinstall.js')}catch{process.exit(0)};import('./dist/postinstall.js')\" || exit 0"
|
|
79
80
|
}
|
|
80
|
-
}
|
|
81
|
+
}
|