@paybond/kit 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -47,6 +47,60 @@ export class SignalHttpError extends Error {
47
47
  this.bodyText = init.bodyText;
48
48
  }
49
49
  }
50
+ const agentRecognitionProofHeader = "x-paybond-agent-recognition-proof";
51
+ export class A2AHttpError extends Error {
52
+ statusCode;
53
+ url;
54
+ bodyText;
55
+ constructor(message, init) {
56
+ super(message);
57
+ this.name = "A2AHttpError";
58
+ this.statusCode = init.statusCode;
59
+ this.url = init.url;
60
+ this.bodyText = init.bodyText;
61
+ }
62
+ }
63
+ export class ProtocolHttpError extends Error {
64
+ statusCode;
65
+ url;
66
+ bodyText;
67
+ errorCode;
68
+ errorMessage;
69
+ constructor(message, init) {
70
+ super(message);
71
+ this.name = "ProtocolHttpError";
72
+ this.statusCode = init.statusCode;
73
+ this.url = init.url;
74
+ this.bodyText = init.bodyText;
75
+ const parsed = parseGatewayErrorEnvelope(init.bodyText);
76
+ this.errorCode = init.errorCode ?? parsed.errorCode;
77
+ this.errorMessage = init.errorMessage ?? parsed.errorMessage;
78
+ }
79
+ }
80
+ function parseGatewayErrorEnvelope(text) {
81
+ if (!text.trim().startsWith("{")) {
82
+ return {};
83
+ }
84
+ try {
85
+ const body = JSON.parse(text);
86
+ if (body === null || Array.isArray(body) || typeof body !== "object") {
87
+ return {};
88
+ }
89
+ const errorCode = typeof body.error === "string" && body.error.trim() ? body.error.trim() : undefined;
90
+ const errorMessage = typeof body.message === "string" && body.message.trim() ? body.message.trim() : undefined;
91
+ return { errorCode, errorMessage };
92
+ }
93
+ catch {
94
+ return {};
95
+ }
96
+ }
97
+ function protocolHTTPErrorMessage(prefix, statusCode, bodyText) {
98
+ const parsed = parseGatewayErrorEnvelope(bodyText);
99
+ if (parsed.errorCode) {
100
+ return `${prefix} HTTP ${statusCode} (${parsed.errorCode}): ${parsed.errorMessage ?? bodyText}`;
101
+ }
102
+ return `${prefix} HTTP ${statusCode}: ${bodyText}`;
103
+ }
50
104
  function normalizeBase(url) {
51
105
  return url.trim().replace(/\/+$/, "");
52
106
  }
@@ -605,6 +659,16 @@ export class HarborClient {
605
659
  }
606
660
  const DEFAULT_PRINCIPAL_PATH = "/v1/auth/principal";
607
661
  const SETTLEMENT_RAIL_VALUES = new Set(["stripe_connect", "x402_usdc_base"]);
662
+ const FRAUD_REVIEW_EVENT_TYPES = new Set([
663
+ "review_open_requested",
664
+ "appeal_requested",
665
+ "replay_requested",
666
+ "review_outcome_recorded",
667
+ "confirmed_risk",
668
+ "false_positive",
669
+ "needs_more_evidence",
670
+ ]);
671
+ const FRAUD_REVIEW_OUTCOMES = new Set(["confirmed_risk", "false_positive", "needs_more_evidence"]);
608
672
  function assertJSONObject(value) {
609
673
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
610
674
  throw new Error("expected JSON object");
@@ -770,6 +834,21 @@ export class GatewaySignalClient {
770
834
  this.assertTenant(body, url);
771
835
  return body;
772
836
  }
837
+ async getSignedPortfolioArtifact(scoreVersion) {
838
+ const url = `${this.base}signal/v1/portfolio/signed-export${this.scoreQuery(scoreVersion)}`;
839
+ const res = await this.fetchGetWithRetries(url);
840
+ const text = await res.text();
841
+ if (!res.ok) {
842
+ throw new SignalHttpError(`Signal signed portfolio artifact HTTP ${res.status}: ${text}`, {
843
+ statusCode: res.status,
844
+ url,
845
+ bodyText: text,
846
+ });
847
+ }
848
+ const body = assertJSONObject(JSON.parse(text));
849
+ this.assertTenant(body, url);
850
+ return body;
851
+ }
773
852
  async getOperatorExplanation(operatorDid, scoreVersion) {
774
853
  const enc = encodeURIComponent(operatorDid);
775
854
  const url = `${this.base}signal/v1/operators/${enc}/explanation${this.scoreQuery(scoreVersion)}`;
@@ -815,6 +894,553 @@ export class GatewaySignalClient {
815
894
  return body;
816
895
  }
817
896
  }
897
+ /**
898
+ * Tenant-bound client for gateway fraud review and metrics routes.
899
+ */
900
+ export class GatewayFraudClient {
901
+ base;
902
+ tenantId;
903
+ bearerToken;
904
+ maxRetries;
905
+ constructor(gatewayBaseUrl, tenantId, options) {
906
+ this.base = normalizeBase(gatewayBaseUrl) + "/";
907
+ this.tenantId = tenantId.trim();
908
+ this.bearerToken = options.staticGatewayBearerToken.trim();
909
+ this.maxRetries = Math.max(1, options.maxRetries ?? 3);
910
+ }
911
+ async fetchGetWithRetries(url) {
912
+ let lastErr;
913
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
914
+ let res;
915
+ try {
916
+ res = await fetch(url, {
917
+ method: "GET",
918
+ headers: {
919
+ accept: "application/json",
920
+ authorization: `Bearer ${this.bearerToken}`,
921
+ },
922
+ });
923
+ }
924
+ catch (e) {
925
+ lastErr = e;
926
+ if (attempt + 1 >= this.maxRetries)
927
+ throw e;
928
+ await new Promise((r) => setTimeout(r, backoffMs(attempt)));
929
+ continue;
930
+ }
931
+ if ([429, 500, 502, 503, 504].includes(res.status)) {
932
+ if (attempt + 1 >= this.maxRetries) {
933
+ return res;
934
+ }
935
+ const raSec = parseRetryAfterSeconds(res.headers.get("retry-after"));
936
+ const delayMs = raSec != null ? raSec * 1000 : backoffMs(attempt);
937
+ await new Promise((r) => setTimeout(r, delayMs));
938
+ continue;
939
+ }
940
+ return res;
941
+ }
942
+ throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
943
+ }
944
+ async fetchPostJSON(url, payload) {
945
+ return fetch(url, {
946
+ method: "POST",
947
+ headers: {
948
+ accept: "application/json",
949
+ authorization: `Bearer ${this.bearerToken}`,
950
+ "content-type": "application/json",
951
+ },
952
+ body: JSON.stringify(payload),
953
+ });
954
+ }
955
+ async fetchPutJSON(url, payload) {
956
+ return fetch(url, {
957
+ method: "PUT",
958
+ headers: {
959
+ accept: "application/json",
960
+ authorization: `Bearer ${this.bearerToken}`,
961
+ "content-type": "application/json",
962
+ },
963
+ body: JSON.stringify(payload),
964
+ });
965
+ }
966
+ assertTenant(body, url) {
967
+ const tid = String(body.tenant_id ?? "");
968
+ if (tid !== this.tenantId) {
969
+ throw new Error(`fraud tenant mismatch: client=${this.tenantId} gateway=${tid} url=${url}`);
970
+ }
971
+ }
972
+ assertOperator(body, operatorDid, label) {
973
+ const echoedOperator = String(body.operator_did ?? "");
974
+ if (echoedOperator !== operatorDid) {
975
+ throw new Error(`fraud ${label} operator mismatch: requested=${operatorDid} gateway=${echoedOperator}`);
976
+ }
977
+ }
978
+ query(params) {
979
+ const qs = new URLSearchParams();
980
+ for (const [key, value] of Object.entries(params)) {
981
+ if (typeof value === "number") {
982
+ qs.set(key, String(value));
983
+ continue;
984
+ }
985
+ if (typeof value === "string" && value.trim()) {
986
+ qs.set(key, value.trim());
987
+ }
988
+ }
989
+ const raw = qs.toString();
990
+ return raw ? `?${raw}` : "";
991
+ }
992
+ normalizedSeverity(severity) {
993
+ const normalized = severity?.trim();
994
+ if (!normalized) {
995
+ return undefined;
996
+ }
997
+ if (!["elevated", "high", "critical"].includes(normalized)) {
998
+ throw new Error("fraud severity must be one of elevated, high, or critical");
999
+ }
1000
+ return normalized;
1001
+ }
1002
+ normalizedWindow(window) {
1003
+ const normalized = window?.trim();
1004
+ if (!normalized) {
1005
+ return undefined;
1006
+ }
1007
+ if (!["24h", "7d", "30d"].includes(normalized)) {
1008
+ throw new Error("fraud metrics window must be one of 24h, 7d, or 30d");
1009
+ }
1010
+ return normalized;
1011
+ }
1012
+ normalizedReleaseGateMode(mode) {
1013
+ const normalized = mode.trim();
1014
+ if (!["review_only", "critical_hold"].includes(normalized)) {
1015
+ throw new Error("fraud release gate mode must be one of review_only or critical_hold");
1016
+ }
1017
+ return normalized;
1018
+ }
1019
+ normalizedReviewEventType(eventType) {
1020
+ const normalized = eventType.trim();
1021
+ if (!FRAUD_REVIEW_EVENT_TYPES.has(normalized)) {
1022
+ throw new Error("fraud review eventType must be one of review_open_requested, appeal_requested, replay_requested, review_outcome_recorded, confirmed_risk, false_positive, or needs_more_evidence");
1023
+ }
1024
+ return normalized;
1025
+ }
1026
+ normalizedReviewOutcome(outcome) {
1027
+ const normalized = outcome.trim();
1028
+ if (!FRAUD_REVIEW_OUTCOMES.has(normalized)) {
1029
+ throw new Error("fraud review outcome must be one of confirmed_risk, false_positive, or needs_more_evidence");
1030
+ }
1031
+ return normalized;
1032
+ }
1033
+ optionalReviewContext(value) {
1034
+ const normalized = value?.trim();
1035
+ return normalized || undefined;
1036
+ }
1037
+ async getFraudAssessment(operatorDid, scoreVersion) {
1038
+ const enc = encodeURIComponent(operatorDid);
1039
+ const url = `${this.base}signal/v1/operators/${enc}/review-status${this.query({
1040
+ score_version: scoreVersion,
1041
+ })}`;
1042
+ const res = await this.fetchGetWithRetries(url);
1043
+ const text = await res.text();
1044
+ if (res.status === 404) {
1045
+ return null;
1046
+ }
1047
+ if (!res.ok) {
1048
+ throw new SignalHttpError(`Fraud assessment HTTP ${res.status}: ${text}`, {
1049
+ statusCode: res.status,
1050
+ url,
1051
+ bodyText: text,
1052
+ });
1053
+ }
1054
+ const body = assertJSONObject(JSON.parse(text));
1055
+ this.assertTenant(body, url);
1056
+ this.assertOperator(body, operatorDid, "assessment");
1057
+ return body;
1058
+ }
1059
+ async listFraudReviewQueue(options = {}) {
1060
+ let rawLimit;
1061
+ if (options.limit !== undefined) {
1062
+ if (!Number.isFinite(options.limit)) {
1063
+ throw new Error("fraud review queue limit must be a finite number");
1064
+ }
1065
+ rawLimit = Math.max(1, Math.min(Math.floor(options.limit), 500));
1066
+ }
1067
+ const url = `${this.base}signal/v1/review-queue${this.query({
1068
+ state: options.state,
1069
+ fraud_severity: this.normalizedSeverity(options.severity),
1070
+ limit: rawLimit,
1071
+ score_version: options.scoreVersion,
1072
+ })}`;
1073
+ const res = await this.fetchGetWithRetries(url);
1074
+ const text = await res.text();
1075
+ if (!res.ok) {
1076
+ throw new SignalHttpError(`Fraud review queue HTTP ${res.status}: ${text}`, {
1077
+ statusCode: res.status,
1078
+ url,
1079
+ bodyText: text,
1080
+ });
1081
+ }
1082
+ const body = assertJSONObject(JSON.parse(text));
1083
+ this.assertTenant(body, url);
1084
+ return body;
1085
+ }
1086
+ async getFraudMetrics(options = {}) {
1087
+ const url = `${this.base}signal/v1/fraud/metrics${this.query({
1088
+ window: this.normalizedWindow(options.window),
1089
+ score_version: options.scoreVersion,
1090
+ })}`;
1091
+ const res = await this.fetchGetWithRetries(url);
1092
+ const text = await res.text();
1093
+ if (!res.ok) {
1094
+ throw new SignalHttpError(`Fraud metrics HTTP ${res.status}: ${text}`, {
1095
+ statusCode: res.status,
1096
+ url,
1097
+ bodyText: text,
1098
+ });
1099
+ }
1100
+ const body = assertJSONObject(JSON.parse(text));
1101
+ this.assertTenant(body, url);
1102
+ return body;
1103
+ }
1104
+ async getFraudReleaseGateConfig(scoreVersion) {
1105
+ const url = `${this.base}signal/v1/fraud/release-gate${this.query({
1106
+ score_version: scoreVersion,
1107
+ })}`;
1108
+ const res = await this.fetchGetWithRetries(url);
1109
+ const text = await res.text();
1110
+ if (!res.ok) {
1111
+ throw new SignalHttpError(`Fraud release gate HTTP ${res.status}: ${text}`, {
1112
+ statusCode: res.status,
1113
+ url,
1114
+ bodyText: text,
1115
+ });
1116
+ }
1117
+ const body = assertJSONObject(JSON.parse(text));
1118
+ this.assertTenant(body, url);
1119
+ return body;
1120
+ }
1121
+ async setFraudReleaseGateMode(mode) {
1122
+ const normalized = this.normalizedReleaseGateMode(mode);
1123
+ const url = `${this.base}signal/v1/fraud/release-gate`;
1124
+ const res = await this.fetchPutJSON(url, { mode: normalized });
1125
+ const text = await res.text();
1126
+ if (!res.ok) {
1127
+ throw new SignalHttpError(`Fraud release gate update HTTP ${res.status}: ${text}`, {
1128
+ statusCode: res.status,
1129
+ url,
1130
+ bodyText: text,
1131
+ });
1132
+ }
1133
+ const body = assertJSONObject(JSON.parse(text));
1134
+ this.assertTenant(body, url);
1135
+ return body;
1136
+ }
1137
+ async recordFraudReviewEvent(operatorDid, event, scoreVersion) {
1138
+ let eventType = this.normalizedReviewEventType(event.eventType);
1139
+ let reviewOutcome = event.reviewOutcome ?? event.review_outcome;
1140
+ if (FRAUD_REVIEW_OUTCOMES.has(eventType)) {
1141
+ reviewOutcome = eventType;
1142
+ eventType = "review_outcome_recorded";
1143
+ }
1144
+ const normalizedOutcome = reviewOutcome === undefined ? undefined : this.normalizedReviewOutcome(reviewOutcome);
1145
+ if (eventType === "review_outcome_recorded" && normalizedOutcome === undefined) {
1146
+ throw new Error("fraud review outcome must be one of confirmed_risk, false_positive, or needs_more_evidence");
1147
+ }
1148
+ const signalCode = this.optionalReviewContext(event.signalCode ?? event.signal_code);
1149
+ const intentId = this.optionalReviewContext(event.intentId ?? event.intent_id);
1150
+ const providerEventId = this.optionalReviewContext(event.providerEventId ?? event.provider_event_id);
1151
+ const enc = encodeURIComponent(operatorDid);
1152
+ const url = `${this.base}signal/v1/operators/${enc}/review-events${this.query({
1153
+ score_version: scoreVersion,
1154
+ })}`;
1155
+ const res = await this.fetchPostJSON(url, {
1156
+ event_type: eventType,
1157
+ ...(normalizedOutcome ? { review_outcome: normalizedOutcome } : {}),
1158
+ ...(signalCode ? { signal_code: signalCode } : {}),
1159
+ ...(intentId ? { intent_id: intentId } : {}),
1160
+ ...(providerEventId ? { provider_event_id: providerEventId } : {}),
1161
+ summary: event.summary,
1162
+ });
1163
+ const text = await res.text();
1164
+ if (!res.ok && res.status !== 429) {
1165
+ throw new SignalHttpError(`Fraud review event HTTP ${res.status}: ${text}`, {
1166
+ statusCode: res.status,
1167
+ url,
1168
+ bodyText: text,
1169
+ });
1170
+ }
1171
+ const body = assertJSONObject(JSON.parse(text));
1172
+ this.assertTenant(body, url);
1173
+ this.assertOperator(body, operatorDid, "review event");
1174
+ return body;
1175
+ }
1176
+ }
1177
+ /**
1178
+ * Public or optionally authenticated reader for the gateway's A2A discovery surface.
1179
+ */
1180
+ export class GatewayA2AClient {
1181
+ base;
1182
+ bearerToken;
1183
+ maxRetries;
1184
+ constructor(gatewayBaseUrl, options) {
1185
+ this.base = normalizeBase(gatewayBaseUrl) + "/";
1186
+ this.bearerToken = options?.staticGatewayBearerToken?.trim() || undefined;
1187
+ this.maxRetries = Math.max(1, options?.maxRetries ?? 3);
1188
+ }
1189
+ async fetchGetWithRetries(url) {
1190
+ let lastErr;
1191
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
1192
+ let res;
1193
+ try {
1194
+ const headers = new Headers({
1195
+ accept: "application/json",
1196
+ });
1197
+ if (this.bearerToken) {
1198
+ headers.set("authorization", `Bearer ${this.bearerToken}`);
1199
+ }
1200
+ res = await fetch(url, {
1201
+ method: "GET",
1202
+ headers,
1203
+ });
1204
+ }
1205
+ catch (e) {
1206
+ lastErr = e;
1207
+ if (attempt + 1 >= this.maxRetries)
1208
+ throw e;
1209
+ await new Promise((r) => setTimeout(r, backoffMs(attempt)));
1210
+ continue;
1211
+ }
1212
+ if ([429, 500, 502, 503, 504].includes(res.status)) {
1213
+ if (attempt + 1 >= this.maxRetries) {
1214
+ return res;
1215
+ }
1216
+ const raSec = parseRetryAfterSeconds(res.headers.get("retry-after"));
1217
+ const delayMs = raSec != null ? raSec * 1000 : backoffMs(attempt);
1218
+ await new Promise((r) => setTimeout(r, delayMs));
1219
+ continue;
1220
+ }
1221
+ return res;
1222
+ }
1223
+ throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
1224
+ }
1225
+ async getAgentCard() {
1226
+ const url = `${this.base}.well-known/agent-card.json`;
1227
+ const res = await this.fetchGetWithRetries(url);
1228
+ const text = await res.text();
1229
+ if (!res.ok) {
1230
+ throw new A2AHttpError(`A2A agent card HTTP ${res.status}: ${text}`, {
1231
+ statusCode: res.status,
1232
+ url,
1233
+ bodyText: text,
1234
+ });
1235
+ }
1236
+ return assertJSONObject(JSON.parse(text));
1237
+ }
1238
+ async getTaskContracts() {
1239
+ const url = `${this.base}protocol/v2/a2a/task-contracts`;
1240
+ const res = await this.fetchGetWithRetries(url);
1241
+ const text = await res.text();
1242
+ if (!res.ok) {
1243
+ throw new A2AHttpError(`A2A task contracts HTTP ${res.status}: ${text}`, {
1244
+ statusCode: res.status,
1245
+ url,
1246
+ bodyText: text,
1247
+ });
1248
+ }
1249
+ return assertJSONObject(JSON.parse(text));
1250
+ }
1251
+ async getTaskContract(contractId) {
1252
+ const enc = encodeURIComponent(contractId);
1253
+ const url = `${this.base}protocol/v2/a2a/task-contracts/${enc}`;
1254
+ const res = await this.fetchGetWithRetries(url);
1255
+ const text = await res.text();
1256
+ if (!res.ok) {
1257
+ throw new A2AHttpError(`A2A task contract HTTP ${res.status}: ${text}`, {
1258
+ statusCode: res.status,
1259
+ url,
1260
+ bodyText: text,
1261
+ });
1262
+ }
1263
+ return assertJSONObject(JSON.parse(text));
1264
+ }
1265
+ }
1266
+ export class GatewayProtocolClient {
1267
+ tenantId;
1268
+ base;
1269
+ staticGatewayBearerToken;
1270
+ maxRetries;
1271
+ constructor(gatewayBaseUrl, tenantId, init) {
1272
+ this.base = `${normalizeBase(gatewayBaseUrl)}/`;
1273
+ this.tenantId = tenantId.trim();
1274
+ this.staticGatewayBearerToken = init?.staticGatewayBearerToken?.trim() || null;
1275
+ this.maxRetries = Math.max(1, init?.maxRetries ?? 3);
1276
+ }
1277
+ async fetchWithRetries(url, init) {
1278
+ let lastErr;
1279
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
1280
+ let res;
1281
+ try {
1282
+ res = await fetch(url, init);
1283
+ }
1284
+ catch (e) {
1285
+ lastErr = e;
1286
+ if (attempt + 1 >= this.maxRetries) {
1287
+ throw e;
1288
+ }
1289
+ await new Promise((r) => setTimeout(r, backoffMs(attempt)));
1290
+ continue;
1291
+ }
1292
+ if ([429, 500, 502, 503, 504].includes(res.status) && attempt + 1 < this.maxRetries) {
1293
+ const raSec = parseRetryAfterSeconds(res.headers.get("retry-after"));
1294
+ const delayMs = raSec != null ? raSec * 1000 : backoffMs(attempt);
1295
+ await new Promise((r) => setTimeout(r, delayMs));
1296
+ continue;
1297
+ }
1298
+ return res;
1299
+ }
1300
+ throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
1301
+ }
1302
+ headers(extra) {
1303
+ const headers = new Headers(extra);
1304
+ headers.set("accept", "application/json");
1305
+ headers.set("x-tenant-id", this.tenantId);
1306
+ if (this.staticGatewayBearerToken) {
1307
+ headers.set("authorization", `Bearer ${this.staticGatewayBearerToken}`);
1308
+ }
1309
+ return headers;
1310
+ }
1311
+ async postJSON(path, payload, extraHeaders) {
1312
+ const url = `${this.base}${path.replace(/^\/+/, "")}`;
1313
+ const res = await this.fetchWithRetries(url, {
1314
+ method: "POST",
1315
+ headers: this.headers({
1316
+ "content-type": "application/json",
1317
+ ...(extraHeaders ?? {}),
1318
+ }),
1319
+ body: JSON.stringify(payload),
1320
+ });
1321
+ const text = await res.text();
1322
+ if (!res.ok) {
1323
+ const parsed = parseGatewayErrorEnvelope(text);
1324
+ throw new ProtocolHttpError(protocolHTTPErrorMessage(`gateway POST ${path}`, res.status, text), {
1325
+ statusCode: res.status,
1326
+ url,
1327
+ bodyText: text,
1328
+ errorCode: parsed.errorCode,
1329
+ errorMessage: parsed.errorMessage,
1330
+ });
1331
+ }
1332
+ return assertJSONObject(JSON.parse(text));
1333
+ }
1334
+ async importAgentMandateV1(init) {
1335
+ const url = `${this.base}protocol/v2/mandates`;
1336
+ const res = await this.fetchWithRetries(url, {
1337
+ method: "POST",
1338
+ headers: this.headers({ "content-type": "application/json" }),
1339
+ body: JSON.stringify({
1340
+ signed_mandate: init.signedMandate,
1341
+ intent_id: init.intentId,
1342
+ transport_binding: init.transportBinding ?? {},
1343
+ recognition_proof: init.recognitionProof,
1344
+ }),
1345
+ });
1346
+ const text = await res.text();
1347
+ if (!res.ok) {
1348
+ const parsed = parseGatewayErrorEnvelope(text);
1349
+ throw new ProtocolHttpError(protocolHTTPErrorMessage("protocol mandate import", res.status, text), {
1350
+ statusCode: res.status,
1351
+ url,
1352
+ bodyText: text,
1353
+ errorCode: parsed.errorCode,
1354
+ errorMessage: parsed.errorMessage,
1355
+ });
1356
+ }
1357
+ const body = assertJSONObject(JSON.parse(text));
1358
+ if (String(body.intent_id ?? "").trim() !== init.intentId) {
1359
+ throw new Error(`protocol intent mismatch: requested=${init.intentId} gateway=${String(body.intent_id ?? "")}`);
1360
+ }
1361
+ if (String(body.mandate?.authorization?.tenant_id ?? "").trim() !== this.tenantId) {
1362
+ throw new Error(`protocol mandate tenant mismatch: client=${this.tenantId} gateway=${String(body.mandate?.authorization?.tenant_id ?? "")}`);
1363
+ }
1364
+ return body;
1365
+ }
1366
+ async getSettlementReceiptV1(receiptId) {
1367
+ const enc = encodeURIComponent(receiptId);
1368
+ const url = `${this.base}protocol/v2/receipts/${enc}`;
1369
+ const res = await this.fetchWithRetries(url, {
1370
+ method: "GET",
1371
+ headers: this.headers(),
1372
+ });
1373
+ const text = await res.text();
1374
+ if (!res.ok) {
1375
+ const parsed = parseGatewayErrorEnvelope(text);
1376
+ throw new ProtocolHttpError(protocolHTTPErrorMessage("protocol settlement receipt", res.status, text), {
1377
+ statusCode: res.status,
1378
+ url,
1379
+ bodyText: text,
1380
+ errorCode: parsed.errorCode,
1381
+ errorMessage: parsed.errorMessage,
1382
+ });
1383
+ }
1384
+ const body = assertJSONObject(JSON.parse(text));
1385
+ if (String(body.receipt_id ?? "").trim() !== receiptId) {
1386
+ throw new Error(`protocol receipt mismatch: requested=${receiptId} gateway=${String(body.receipt_id ?? "")}`);
1387
+ }
1388
+ if (String(body.tenant_id ?? "").trim() !== this.tenantId) {
1389
+ throw new Error(`protocol receipt tenant mismatch: client=${this.tenantId} gateway=${String(body.tenant_id ?? "")}`);
1390
+ }
1391
+ return body;
1392
+ }
1393
+ async verifyProtocolReceiptV1(receipt) {
1394
+ const url = `${this.base}protocol/v2/receipts/verify`;
1395
+ const res = await this.fetchWithRetries(url, {
1396
+ method: "POST",
1397
+ headers: this.headers({ "content-type": "application/json" }),
1398
+ body: JSON.stringify(receipt),
1399
+ });
1400
+ const text = await res.text();
1401
+ if (!res.ok) {
1402
+ const parsed = parseGatewayErrorEnvelope(text);
1403
+ throw new ProtocolHttpError(protocolHTTPErrorMessage("protocol receipt verify", res.status, text), {
1404
+ statusCode: res.status,
1405
+ url,
1406
+ bodyText: text,
1407
+ errorCode: parsed.errorCode,
1408
+ errorMessage: parsed.errorMessage,
1409
+ });
1410
+ }
1411
+ return assertJSONObject(JSON.parse(text));
1412
+ }
1413
+ async createHarborIntent(init) {
1414
+ return this.postJSON("/harbor/intents", init.body, gatewayMutationHeaders(init.recognitionProof, {
1415
+ ...(init.idempotencyKey?.trim() ? { "idempotency-key": init.idempotencyKey.trim() } : {}),
1416
+ }));
1417
+ }
1418
+ async fundHarborIntent(init) {
1419
+ return this.postJSON(`/harbor/intents/${encodeURIComponent(init.intentId)}/fund`, {}, gatewayMutationHeaders(init.recognitionProof, {
1420
+ ...(init.paymentSignature?.trim() ? { "payment-signature": init.paymentSignature.trim() } : {}),
1421
+ ...(init.idempotencyKey?.trim() ? { "idempotency-key": init.idempotencyKey.trim() } : {}),
1422
+ }));
1423
+ }
1424
+ async submitHarborEvidence(init) {
1425
+ return this.postJSON(`/harbor/intents/${encodeURIComponent(init.intentId)}/evidence`, init.body, gatewayMutationHeaders(init.recognitionProof, {
1426
+ ...(init.idempotencyKey?.trim() ? { "idempotency-key": init.idempotencyKey.trim() } : {}),
1427
+ }));
1428
+ }
1429
+ async confirmHarborSettlement(init) {
1430
+ return this.postJSON(`/harbor/intents/${encodeURIComponent(init.intentId)}/settlement/confirm`, init.body, gatewayMutationHeaders(init.recognitionProof, {
1431
+ ...(init.idempotencyKey?.trim() ? { "idempotency-key": init.idempotencyKey.trim() } : {}),
1432
+ }));
1433
+ }
1434
+ }
1435
+ function gatewayMutationHeaders(recognitionProof, headers) {
1436
+ return {
1437
+ ...(headers ?? {}),
1438
+ [agentRecognitionProofHeader]: encodeRecognitionProofHeader(recognitionProof),
1439
+ };
1440
+ }
1441
+ function encodeRecognitionProofHeader(proof) {
1442
+ return Buffer.from(JSON.stringify(proof), "utf8").toString("base64url");
1443
+ }
818
1444
  async function resolveGatewayTenantId(gatewayBaseUrl, apiKey, principalPath, maxRetries) {
819
1445
  const base = normalizeBase(gatewayBaseUrl);
820
1446
  const path = principalPath.startsWith("/") ? principalPath : `/${principalPath}`;
@@ -882,6 +1508,25 @@ export class ServiceAccountSignalSession {
882
1508
  await Promise.resolve();
883
1509
  }
884
1510
  }
1511
+ /**
1512
+ * Tenant-bound fraud review and metrics session for one service-account API key.
1513
+ */
1514
+ export class ServiceAccountFraudSession {
1515
+ fraud;
1516
+ constructor(fraud) {
1517
+ this.fraud = fraud;
1518
+ }
1519
+ static async open(init) {
1520
+ const tenantId = await resolveGatewayTenantId(init.gatewayBaseUrl, init.apiKey, init.principalPath ?? DEFAULT_PRINCIPAL_PATH, Math.max(1, init.maxRetries ?? 3));
1521
+ return new ServiceAccountFraudSession(new GatewayFraudClient(init.gatewayBaseUrl, tenantId, {
1522
+ staticGatewayBearerToken: init.apiKey,
1523
+ maxRetries: init.maxRetries ?? 3,
1524
+ }));
1525
+ }
1526
+ async aclose() {
1527
+ await Promise.resolve();
1528
+ }
1529
+ }
885
1530
  /**
886
1531
  * Ergonomic intent helpers: principal-signed intent create, x402 funding, and payee-signed evidence.
887
1532
  */
@@ -931,12 +1576,18 @@ export class PaybondIntents {
931
1576
  export class Paybond {
932
1577
  harbor;
933
1578
  signal;
1579
+ fraud;
1580
+ a2a;
1581
+ protocol;
934
1582
  intents;
935
1583
  session;
936
- constructor(session, signal) {
1584
+ constructor(session, signal, fraud, a2a, protocol) {
937
1585
  this.session = session;
938
1586
  this.harbor = session.harbor;
939
1587
  this.signal = signal;
1588
+ this.fraud = fraud;
1589
+ this.a2a = a2a;
1590
+ this.protocol = protocol;
940
1591
  this.intents = new PaybondIntents(session.harbor);
941
1592
  }
942
1593
  /** Open a tenant-bound session via gateway `harbor-access` exchange. */
@@ -946,7 +1597,19 @@ export class Paybond {
946
1597
  staticGatewayBearerToken: init.apiKey,
947
1598
  maxRetries: init.maxRetries ?? 3,
948
1599
  });
949
- return new Paybond(session, signal);
1600
+ const fraud = new GatewayFraudClient(init.gatewayBaseUrl, session.harbor.tenantId, {
1601
+ staticGatewayBearerToken: init.apiKey,
1602
+ maxRetries: init.maxRetries ?? 3,
1603
+ });
1604
+ const a2a = new GatewayA2AClient(init.gatewayBaseUrl, {
1605
+ staticGatewayBearerToken: init.apiKey,
1606
+ maxRetries: init.maxRetries ?? 3,
1607
+ });
1608
+ const protocol = new GatewayProtocolClient(init.gatewayBaseUrl, session.harbor.tenantId, {
1609
+ staticGatewayBearerToken: init.apiKey,
1610
+ maxRetries: init.maxRetries ?? 3,
1611
+ });
1612
+ return new Paybond(session, signal, fraud, a2a, protocol);
950
1613
  }
951
1614
  async rotateHarborToken() {
952
1615
  await this.session.rotateHarborToken();