@forum-labs/payfetch 1.0.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/LICENSE +21 -0
- package/README.md +467 -0
- package/dist/bin/payfetch-mcp.d.ts +2 -0
- package/dist/bin/payfetch-mcp.js +9 -0
- package/dist/bin/payfetch.d.ts +2 -0
- package/dist/bin/payfetch.js +126 -0
- package/dist/src/config.d.ts +35 -0
- package/dist/src/config.js +170 -0
- package/dist/src/core/budget.d.ts +62 -0
- package/dist/src/core/budget.js +236 -0
- package/dist/src/core/constants.d.ts +66 -0
- package/dist/src/core/constants.js +115 -0
- package/dist/src/core/fs.d.ts +14 -0
- package/dist/src/core/fs.js +104 -0
- package/dist/src/core/integrity.d.ts +23 -0
- package/dist/src/core/integrity.js +150 -0
- package/dist/src/core/ledger.d.ts +149 -0
- package/dist/src/core/ledger.js +357 -0
- package/dist/src/core/notes.d.ts +22 -0
- package/dist/src/core/notes.js +24 -0
- package/dist/src/core/pipeline.d.ts +143 -0
- package/dist/src/core/pipeline.js +795 -0
- package/dist/src/core/policy.d.ts +57 -0
- package/dist/src/core/policy.js +224 -0
- package/dist/src/core/transport.d.ts +93 -0
- package/dist/src/core/transport.js +364 -0
- package/dist/src/guards/index.d.ts +4 -0
- package/dist/src/guards/index.js +3 -0
- package/dist/src/guards/internal.d.ts +17 -0
- package/dist/src/guards/internal.js +74 -0
- package/dist/src/guards/safety.d.ts +2 -0
- package/dist/src/guards/safety.js +94 -0
- package/dist/src/guards/trust.d.ts +2 -0
- package/dist/src/guards/trust.js +79 -0
- package/dist/src/guards/types.d.ts +59 -0
- package/dist/src/guards/types.js +20 -0
- package/dist/src/index.d.ts +58 -0
- package/dist/src/index.js +151 -0
- package/dist/src/mcp/server.d.ts +6 -0
- package/dist/src/mcp/server.js +125 -0
- package/dist/src/mcp/tools.d.ts +46 -0
- package/dist/src/mcp/tools.js +321 -0
- package/dist/src/payer/mpp.d.ts +7 -0
- package/dist/src/payer/mpp.js +13 -0
- package/dist/src/payer/parse402.d.ts +6 -0
- package/dist/src/payer/parse402.js +169 -0
- package/dist/src/payer/signer_cdp.d.ts +14 -0
- package/dist/src/payer/signer_cdp.js +64 -0
- package/dist/src/payer/signer_local.d.ts +8 -0
- package/dist/src/payer/signer_local.js +14 -0
- package/dist/src/payer/types.d.ts +99 -0
- package/dist/src/payer/types.js +10 -0
- package/dist/src/payer/x402.d.ts +23 -0
- package/dist/src/payer/x402.js +165 -0
- package/dist/src/report/report.d.ts +162 -0
- package/dist/src/report/report.js +220 -0
- package/mcpb/manifest.json +104 -0
- package/package.json +72 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { AUTO_DENY_STRIKES, AUTO_DENY_TTL_DAYS, AUTO_DENY_UNKNOWN_ONLY_STRIKES, AUTO_DENY_WINDOW_DAYS, LOCK_STALE_S, } from "./constants.js";
|
|
3
|
+
import { defaultRandom, genesisMac, lineHash, loadOrCreateKey, nextMac, readSidecarRecords, sidecarPath, verifyMonth, } from "./integrity.js";
|
|
4
|
+
export function isReceipt(e) {
|
|
5
|
+
return e.schema === "p3f.receipt.v1";
|
|
6
|
+
}
|
|
7
|
+
export function isAdjust(e) {
|
|
8
|
+
return e.schema === "p3f.adjust.v1";
|
|
9
|
+
}
|
|
10
|
+
export function utcDate(ms) {
|
|
11
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
12
|
+
}
|
|
13
|
+
export function monthKey(ms) {
|
|
14
|
+
return new Date(ms).toISOString().slice(0, 7);
|
|
15
|
+
}
|
|
16
|
+
export const TOTAL_KEY = "total";
|
|
17
|
+
export function dayKey(date) {
|
|
18
|
+
return `day:${date}`;
|
|
19
|
+
}
|
|
20
|
+
export function hostDayKey(host, date) {
|
|
21
|
+
return `host:${host}:${date}`;
|
|
22
|
+
}
|
|
23
|
+
export function guardDayKey(guardId, date) {
|
|
24
|
+
return `guard:${guardId}:${date}`;
|
|
25
|
+
}
|
|
26
|
+
export function mainCounterKeys(host, ms) {
|
|
27
|
+
const d = utcDate(ms);
|
|
28
|
+
return [dayKey(d), hostDayKey(host, d), TOTAL_KEY];
|
|
29
|
+
}
|
|
30
|
+
const FSYNC_OUTCOMES = new Set([
|
|
31
|
+
"paid_delivered",
|
|
32
|
+
"paid_not_delivered",
|
|
33
|
+
"payment_rejected",
|
|
34
|
+
"unknown_settlement",
|
|
35
|
+
]);
|
|
36
|
+
export function isSettledOutcome(o) {
|
|
37
|
+
return o === "paid_delivered" || o === "paid_not_delivered";
|
|
38
|
+
}
|
|
39
|
+
export function isHoldKeptOutcome(o) {
|
|
40
|
+
return (o === "payment_rejected" || o === "unknown_settlement" || o === "fetch_error");
|
|
41
|
+
}
|
|
42
|
+
export function strikeClassForOutcome(o) {
|
|
43
|
+
if (o === "paid_not_delivered" || o === "payment_rejected")
|
|
44
|
+
return "confirmed";
|
|
45
|
+
if (o === "unknown_settlement")
|
|
46
|
+
return "soft";
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
export class LedgerLockedError extends Error {
|
|
50
|
+
constructor(pid) {
|
|
51
|
+
super(`payfetch: another live instance holds the ledger lock (pid ${pid}); refusing to start (SPEC §8.1).`);
|
|
52
|
+
this.name = "LedgerLockedError";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export class LedgerAppendConflictError extends Error {
|
|
56
|
+
constructor(receiptId) {
|
|
57
|
+
super(`payfetch: refusing to re-append receiptId ${receiptId} — the ledger is append-only (SPEC §8/§14).`);
|
|
58
|
+
this.name = "LedgerAppendConflictError";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export class Ledger {
|
|
62
|
+
#fs;
|
|
63
|
+
#dataDir;
|
|
64
|
+
#now;
|
|
65
|
+
#isPidAlive;
|
|
66
|
+
#random;
|
|
67
|
+
#seenReceiptIds = new Set();
|
|
68
|
+
#holdsLock = false;
|
|
69
|
+
#integrityKey = null;
|
|
70
|
+
#integrityByMonth = new Map();
|
|
71
|
+
constructor(fs, dataDir, now, opts = {}) {
|
|
72
|
+
this.#fs = fs;
|
|
73
|
+
this.#dataDir = dataDir;
|
|
74
|
+
this.#now = now;
|
|
75
|
+
this.#random = opts.random ?? defaultRandom;
|
|
76
|
+
this.#isPidAlive =
|
|
77
|
+
opts.isPidAlive ??
|
|
78
|
+
((pid) => {
|
|
79
|
+
try {
|
|
80
|
+
process.kill(pid, 0);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
return err.code === "EPERM";
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
ledgerDir() {
|
|
89
|
+
return join(this.#dataDir, "ledger");
|
|
90
|
+
}
|
|
91
|
+
monthFile(ms) {
|
|
92
|
+
return join(this.ledgerDir(), `${monthKey(ms)}.jsonl`);
|
|
93
|
+
}
|
|
94
|
+
statePath() {
|
|
95
|
+
return join(this.#dataDir, "state.json");
|
|
96
|
+
}
|
|
97
|
+
lockPath() {
|
|
98
|
+
return join(this.#dataDir, "lock");
|
|
99
|
+
}
|
|
100
|
+
downloadPath(receiptId) {
|
|
101
|
+
return join(this.#dataDir, "downloads", receiptId);
|
|
102
|
+
}
|
|
103
|
+
acquireLock(pid = process.pid) {
|
|
104
|
+
this.#fs.ensureDir(this.#dataDir);
|
|
105
|
+
const path = this.lockPath();
|
|
106
|
+
const contents = JSON.stringify({ pid, ts: this.#now() });
|
|
107
|
+
if (this.#fs.tryCreateExclusive(path, contents)) {
|
|
108
|
+
this.#holdsLock = true;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const raw = this.#fs.readText(path);
|
|
112
|
+
const holder = this.parseLock(raw);
|
|
113
|
+
const stale = holder === null ||
|
|
114
|
+
this.#now() - holder.ts > LOCK_STALE_S * 1000 ||
|
|
115
|
+
!this.#isPidAlive(holder.pid);
|
|
116
|
+
if (!stale)
|
|
117
|
+
throw new LedgerLockedError(holder.pid);
|
|
118
|
+
this.#fs.remove(path);
|
|
119
|
+
if (!this.#fs.tryCreateExclusive(path, contents)) {
|
|
120
|
+
const now2 = this.parseLock(this.#fs.readText(path));
|
|
121
|
+
throw new LedgerLockedError(now2?.pid ?? -1);
|
|
122
|
+
}
|
|
123
|
+
this.#holdsLock = true;
|
|
124
|
+
}
|
|
125
|
+
parseLock(raw) {
|
|
126
|
+
if (raw === null)
|
|
127
|
+
return null;
|
|
128
|
+
try {
|
|
129
|
+
const o = JSON.parse(raw);
|
|
130
|
+
if (typeof o.pid === "number" && typeof o.ts === "number") {
|
|
131
|
+
return { pid: o.pid, ts: o.ts };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
refreshLock(pid = process.pid) {
|
|
139
|
+
if (!this.#holdsLock)
|
|
140
|
+
return;
|
|
141
|
+
this.#fs.writeText(this.lockPath(), JSON.stringify({ pid, ts: this.#now() }));
|
|
142
|
+
}
|
|
143
|
+
releaseLock() {
|
|
144
|
+
if (!this.#holdsLock)
|
|
145
|
+
return;
|
|
146
|
+
this.#fs.remove(this.lockPath());
|
|
147
|
+
this.#holdsLock = false;
|
|
148
|
+
}
|
|
149
|
+
append(receipt) {
|
|
150
|
+
if (this.#seenReceiptIds.has(receipt.receiptId)) {
|
|
151
|
+
throw new LedgerAppendConflictError(receipt.receiptId);
|
|
152
|
+
}
|
|
153
|
+
const fsync = FSYNC_OUTCOMES.has(receipt.outcome) || receipt.payment !== null;
|
|
154
|
+
const json = JSON.stringify(receipt);
|
|
155
|
+
this.#fs.appendLine(this.monthFile(receipt.ts), json, { fsync });
|
|
156
|
+
this.#seenReceiptIds.add(receipt.receiptId);
|
|
157
|
+
this.recordIntegrity(receipt.ts, json, receipt.receiptId, fsync);
|
|
158
|
+
this.refreshLock();
|
|
159
|
+
}
|
|
160
|
+
appendAdjust(adjust) {
|
|
161
|
+
const json = JSON.stringify(adjust);
|
|
162
|
+
this.#fs.appendLine(this.monthFile(adjust.ts), json, { fsync: true });
|
|
163
|
+
this.recordIntegrity(adjust.ts, json, adjust.receiptId, true);
|
|
164
|
+
this.refreshLock();
|
|
165
|
+
}
|
|
166
|
+
recordIntegrity(ts, json, receiptId, fsync) {
|
|
167
|
+
try {
|
|
168
|
+
const key = this.integrityKey();
|
|
169
|
+
const mk = monthKey(ts);
|
|
170
|
+
const mf = this.monthFile(ts);
|
|
171
|
+
let st = this.#integrityByMonth.get(mf);
|
|
172
|
+
if (st === undefined) {
|
|
173
|
+
const records = readSidecarRecords(this.#fs, sidecarPath(mf));
|
|
174
|
+
const prevMac = records.length > 0 ? records[records.length - 1].mac : genesisMac(key, mk);
|
|
175
|
+
st = { prevMac, nextSeq: records.length };
|
|
176
|
+
this.#integrityByMonth.set(mf, st);
|
|
177
|
+
}
|
|
178
|
+
const sha256 = lineHash(json);
|
|
179
|
+
const mac = nextMac(key, st.prevMac, sha256);
|
|
180
|
+
const record = { seq: st.nextSeq, receiptId, sha256, mac };
|
|
181
|
+
this.#fs.appendLine(sidecarPath(mf), JSON.stringify(record), { fsync });
|
|
182
|
+
st.prevMac = mac;
|
|
183
|
+
st.nextSeq += 1;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
integrityKey() {
|
|
189
|
+
if (this.#integrityKey === null) {
|
|
190
|
+
this.#integrityKey = loadOrCreateKey(this.#fs, this.#dataDir, this.#random);
|
|
191
|
+
}
|
|
192
|
+
return this.#integrityKey;
|
|
193
|
+
}
|
|
194
|
+
verifyIntegrity() {
|
|
195
|
+
const key = this.integrityKey();
|
|
196
|
+
const files = this.#fs
|
|
197
|
+
.listDir(this.ledgerDir())
|
|
198
|
+
.filter((f) => f.endsWith(".jsonl"));
|
|
199
|
+
files.sort();
|
|
200
|
+
const months = files.map((f) => {
|
|
201
|
+
const res = verifyMonth(this.#fs, this.#dataDir, key, join(this.ledgerDir(), f));
|
|
202
|
+
return { month: f.replace(/\.jsonl$/, ""), ...res };
|
|
203
|
+
});
|
|
204
|
+
return { ok: months.every((m) => m.ok), months };
|
|
205
|
+
}
|
|
206
|
+
readMonth(ms) {
|
|
207
|
+
const raw = this.#fs.readText(this.monthFile(ms));
|
|
208
|
+
if (raw === null)
|
|
209
|
+
return [];
|
|
210
|
+
const out = [];
|
|
211
|
+
for (const line of raw.split("\n")) {
|
|
212
|
+
const s = line.trim();
|
|
213
|
+
if (s.length === 0)
|
|
214
|
+
continue;
|
|
215
|
+
try {
|
|
216
|
+
const o = JSON.parse(s);
|
|
217
|
+
if (o && (o.schema === "p3f.receipt.v1" || o.schema === "p3f.adjust.v1")) {
|
|
218
|
+
out.push(o);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
readEntries() {
|
|
227
|
+
const now = this.#now();
|
|
228
|
+
const prevMs = new Date(now);
|
|
229
|
+
prevMs.setUTCDate(0);
|
|
230
|
+
const cur = this.monthFile(now);
|
|
231
|
+
const prev = this.monthFile(prevMs.getTime());
|
|
232
|
+
if (cur === prev)
|
|
233
|
+
return this.readMonth(now);
|
|
234
|
+
return [...this.readMonth(prevMs.getTime()), ...this.readMonth(now)];
|
|
235
|
+
}
|
|
236
|
+
readAllReceipts() {
|
|
237
|
+
const files = this.#fs.listDir(this.ledgerDir()).filter((f) => f.endsWith(".jsonl"));
|
|
238
|
+
files.sort();
|
|
239
|
+
const out = [];
|
|
240
|
+
for (const f of files) {
|
|
241
|
+
const raw = this.#fs.readText(join(this.ledgerDir(), f));
|
|
242
|
+
if (raw === null)
|
|
243
|
+
continue;
|
|
244
|
+
for (const line of raw.split("\n")) {
|
|
245
|
+
const s = line.trim();
|
|
246
|
+
if (s.length === 0)
|
|
247
|
+
continue;
|
|
248
|
+
try {
|
|
249
|
+
const o = JSON.parse(s);
|
|
250
|
+
if (o && o.schema === "p3f.receipt.v1")
|
|
251
|
+
out.push(o);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return out;
|
|
258
|
+
}
|
|
259
|
+
loadStateRaw() {
|
|
260
|
+
const raw = this.#fs.readText(this.statePath());
|
|
261
|
+
if (raw === null)
|
|
262
|
+
return null;
|
|
263
|
+
try {
|
|
264
|
+
const o = JSON.parse(raw);
|
|
265
|
+
if (o &&
|
|
266
|
+
o.schema === "p3f.state.v1" &&
|
|
267
|
+
typeof o.installId === "string" &&
|
|
268
|
+
o.counters &&
|
|
269
|
+
Array.isArray(o.holds) &&
|
|
270
|
+
o.autoDeny &&
|
|
271
|
+
Array.isArray(o.approvals)) {
|
|
272
|
+
return o;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
}
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
saveState(state) {
|
|
280
|
+
this.#fs.writeText(this.statePath(), JSON.stringify(state));
|
|
281
|
+
this.refreshLock();
|
|
282
|
+
}
|
|
283
|
+
rebuildState(installId) {
|
|
284
|
+
const entries = this.readEntries();
|
|
285
|
+
const released = new Set();
|
|
286
|
+
const settledOnExpiry = new Set();
|
|
287
|
+
for (const e of entries) {
|
|
288
|
+
if (!isAdjust(e))
|
|
289
|
+
continue;
|
|
290
|
+
if (e.kind === "hold_released_expiry")
|
|
291
|
+
released.add(e.receiptId);
|
|
292
|
+
else if (e.kind === "hold_settled_expiry")
|
|
293
|
+
settledOnExpiry.add(e.receiptId);
|
|
294
|
+
}
|
|
295
|
+
const counters = {};
|
|
296
|
+
const holds = [];
|
|
297
|
+
const autoDeny = {};
|
|
298
|
+
const receipts = entries.filter(isReceipt).sort((a, b) => a.ts - b.ts);
|
|
299
|
+
for (const r of receipts) {
|
|
300
|
+
if (r.payment !== null) {
|
|
301
|
+
const keys = mainCounterKeys(r.host, r.ts);
|
|
302
|
+
if (isSettledOutcome(r.outcome)) {
|
|
303
|
+
const amt = r.payment.settledAmountUsd ?? r.quote?.amountUsd ?? 0;
|
|
304
|
+
for (const k of keys)
|
|
305
|
+
counters[k] = (counters[k] ?? 0) + amt;
|
|
306
|
+
}
|
|
307
|
+
else if (isHoldKeptOutcome(r.outcome)) {
|
|
308
|
+
if (settledOnExpiry.has(r.receiptId)) {
|
|
309
|
+
const amt = r.payment.settledAmountUsd ?? r.quote?.amountUsd ?? 0;
|
|
310
|
+
for (const k of keys)
|
|
311
|
+
counters[k] = (counters[k] ?? 0) + amt;
|
|
312
|
+
}
|
|
313
|
+
else if (released.has(r.receiptId)) {
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
holds.push({
|
|
317
|
+
holdId: r.receiptId,
|
|
318
|
+
amountUsd: r.quote?.amountUsd ?? 0,
|
|
319
|
+
host: r.host,
|
|
320
|
+
validBeforeTs: r.payment.validBeforeTs,
|
|
321
|
+
createdTs: r.ts,
|
|
322
|
+
counterKeys: keys,
|
|
323
|
+
signed: true,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const cls = strikeClassForOutcome(r.outcome);
|
|
329
|
+
if (cls !== null)
|
|
330
|
+
applyStrike(autoDeny, r.host, r.ts, cls);
|
|
331
|
+
}
|
|
332
|
+
return { schema: "p3f.state.v1", installId, counters, holds, autoDeny, approvals: [] };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const DAY_MS = 86_400_000;
|
|
336
|
+
export function applyStrike(autoDeny, host, ts, cls) {
|
|
337
|
+
const entry = autoDeny[host] ?? { strikes: [], deniedUntilTs: null };
|
|
338
|
+
const windowStart = ts - AUTO_DENY_WINDOW_DAYS * DAY_MS;
|
|
339
|
+
entry.strikes = entry.strikes.filter((s) => s.ts >= windowStart);
|
|
340
|
+
entry.strikes.push({ ts, class: cls });
|
|
341
|
+
entry.strikes.sort((a, b) => a.ts - b.ts);
|
|
342
|
+
const total = entry.strikes.length;
|
|
343
|
+
const confirmed = entry.strikes.filter((s) => s.class === "confirmed").length;
|
|
344
|
+
const soft = total - confirmed;
|
|
345
|
+
const engage = (total >= AUTO_DENY_STRIKES && confirmed >= 1) ||
|
|
346
|
+
soft >= AUTO_DENY_UNKNOWN_ONLY_STRIKES;
|
|
347
|
+
if (engage) {
|
|
348
|
+
const lastTs = entry.strikes[entry.strikes.length - 1].ts;
|
|
349
|
+
entry.deniedUntilTs = lastTs + AUTO_DENY_TTL_DAYS * DAY_MS;
|
|
350
|
+
}
|
|
351
|
+
autoDeny[host] = entry;
|
|
352
|
+
return { strikeCount: total, engaged: engage };
|
|
353
|
+
}
|
|
354
|
+
export function isHostAutoDenied(autoDeny, host, now) {
|
|
355
|
+
const e = autoDeny[host];
|
|
356
|
+
return e != null && e.deniedUntilTs != null && now < e.deniedUntilTs;
|
|
357
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { GuardId } from "../guards/types.js";
|
|
2
|
+
export type FixedNoteCode = "malformed_402" | "unsupported_terms" | "unsupported_scheme_upto" | "unknown_asset" | "unsupported_network" | "host_denied" | "host_not_allowlisted" | "host_auto_denied" | "per_call_cap_exceeded" | "guard_unrated" | "approval_mode_deny" | "approval_timeout" | "approval_queue_expired" | "elicit_unsupported" | "elicit_cancelled" | "settlement_unconfirmed" | "body_truncated" | "insecure_redirect" | "private_target_blocked" | "hold_released_expiry" | "test_mode";
|
|
3
|
+
export type BudgetWhich = "day" | "host" | "total";
|
|
4
|
+
export type ElicitFallback = "queue" | "deny";
|
|
5
|
+
export type PreapprovedWhich = "host" | "cap";
|
|
6
|
+
export type BudgetExhaustedNote = `budget_exhausted:${BudgetWhich}`;
|
|
7
|
+
export type PreapprovedNote = `preapproved:${PreapprovedWhich}`;
|
|
8
|
+
export type GuardBlockedNote = `guard_blocked:${GuardId}`;
|
|
9
|
+
export type GuardUnavailableNote = `guard_unavailable:${GuardId}`;
|
|
10
|
+
export type GuardWarnNote = `guard_warn:${GuardId}`;
|
|
11
|
+
export type RedirectedNote = `redirected:${number}`;
|
|
12
|
+
export type AutodenyStrikeNote = `autodeny_strike:${number}`;
|
|
13
|
+
export type ElicitFallbackNote = `elicit_unsupported_fallback:${ElicitFallback}`;
|
|
14
|
+
export type NoteCode = FixedNoteCode | BudgetExhaustedNote | PreapprovedNote | GuardBlockedNote | GuardUnavailableNote | GuardWarnNote | RedirectedNote | AutodenyStrikeNote | ElicitFallbackNote;
|
|
15
|
+
export declare function budgetExhausted(which: BudgetWhich): BudgetExhaustedNote;
|
|
16
|
+
export declare function preapproved(which: PreapprovedWhich): PreapprovedNote;
|
|
17
|
+
export declare function guardBlocked(id: GuardId): GuardBlockedNote;
|
|
18
|
+
export declare function guardUnavailable(id: GuardId): GuardUnavailableNote;
|
|
19
|
+
export declare function guardWarn(id: GuardId): GuardWarnNote;
|
|
20
|
+
export declare function redirected(n: number): RedirectedNote;
|
|
21
|
+
export declare function autodenyStrike(n: number): AutodenyStrikeNote;
|
|
22
|
+
export declare function elicitUnsupportedFallback(mode: ElicitFallback): ElicitFallbackNote;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function budgetExhausted(which) {
|
|
2
|
+
return `budget_exhausted:${which}`;
|
|
3
|
+
}
|
|
4
|
+
export function preapproved(which) {
|
|
5
|
+
return `preapproved:${which}`;
|
|
6
|
+
}
|
|
7
|
+
export function guardBlocked(id) {
|
|
8
|
+
return `guard_blocked:${id}`;
|
|
9
|
+
}
|
|
10
|
+
export function guardUnavailable(id) {
|
|
11
|
+
return `guard_unavailable:${id}`;
|
|
12
|
+
}
|
|
13
|
+
export function guardWarn(id) {
|
|
14
|
+
return `guard_warn:${id}`;
|
|
15
|
+
}
|
|
16
|
+
export function redirected(n) {
|
|
17
|
+
return `redirected:${n}`;
|
|
18
|
+
}
|
|
19
|
+
export function autodenyStrike(n) {
|
|
20
|
+
return `autodeny_strike:${n}`;
|
|
21
|
+
}
|
|
22
|
+
export function elicitUnsupportedFallback(mode) {
|
|
23
|
+
return `elicit_unsupported_fallback:${mode}`;
|
|
24
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { Budget, RemainingBudgets } from "./budget.js";
|
|
2
|
+
import type { PayfetchFs } from "./fs.js";
|
|
3
|
+
import { Ledger, type Outcome, type Receipt } from "./ledger.js";
|
|
4
|
+
import { type Policy } from "./policy.js";
|
|
5
|
+
import { type TransportIo, type TransportResult } from "./transport.js";
|
|
6
|
+
import type { PayfetchDeps, PaymentPayer, PaymentQuote } from "../payer/types.js";
|
|
7
|
+
import type { GuardId, GuardResult, GuardRuntime, PrePayGuard } from "../guards/types.js";
|
|
8
|
+
export type FetchOpts = {
|
|
9
|
+
maxAmountUsd?: number;
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
tokenAddress?: string;
|
|
12
|
+
chain?: string;
|
|
13
|
+
responseMode?: "inline" | "file";
|
|
14
|
+
};
|
|
15
|
+
export type PayfetchFetchResult = {
|
|
16
|
+
response: Response | null;
|
|
17
|
+
receipt: Receipt;
|
|
18
|
+
};
|
|
19
|
+
export type Decision = {
|
|
20
|
+
outcome: Outcome;
|
|
21
|
+
denyCode: string | null;
|
|
22
|
+
decision: "would_pay" | "would_deny" | "free";
|
|
23
|
+
quote: PaymentQuote | null;
|
|
24
|
+
rejectedQuotes: Record<string, number> | null;
|
|
25
|
+
guards: GuardResult[];
|
|
26
|
+
remainingBudgets: RemainingBudgets;
|
|
27
|
+
notes: string[];
|
|
28
|
+
};
|
|
29
|
+
export type SpendStatus = {
|
|
30
|
+
date: string;
|
|
31
|
+
day: {
|
|
32
|
+
spentUsd: number;
|
|
33
|
+
remainingUsd: number;
|
|
34
|
+
};
|
|
35
|
+
total: {
|
|
36
|
+
spentUsd: number;
|
|
37
|
+
remainingUsd: number | null;
|
|
38
|
+
};
|
|
39
|
+
perHost: Record<string, {
|
|
40
|
+
spentUsd: number;
|
|
41
|
+
remainingUsd: number;
|
|
42
|
+
}>;
|
|
43
|
+
holds: {
|
|
44
|
+
holdId: string;
|
|
45
|
+
amountUsd: number;
|
|
46
|
+
host: string;
|
|
47
|
+
validBeforeTs: number;
|
|
48
|
+
}[];
|
|
49
|
+
autoDenied: {
|
|
50
|
+
host: string;
|
|
51
|
+
untilTs: number;
|
|
52
|
+
}[];
|
|
53
|
+
recentPayments: {
|
|
54
|
+
receiptId: string;
|
|
55
|
+
host: string;
|
|
56
|
+
amountUsd: number | null;
|
|
57
|
+
outcome: Outcome;
|
|
58
|
+
ts: number;
|
|
59
|
+
}[];
|
|
60
|
+
};
|
|
61
|
+
export type { GuardRuntime } from "../guards/types.js";
|
|
62
|
+
export type EngineConfig = {
|
|
63
|
+
deps: PayfetchDeps;
|
|
64
|
+
fs: PayfetchFs;
|
|
65
|
+
ledger: Ledger;
|
|
66
|
+
budget: Budget;
|
|
67
|
+
payers: PaymentPayer[];
|
|
68
|
+
policyProvider: () => {
|
|
69
|
+
ok: true;
|
|
70
|
+
policy: Policy;
|
|
71
|
+
} | {
|
|
72
|
+
ok: false;
|
|
73
|
+
error: string;
|
|
74
|
+
};
|
|
75
|
+
transportIo: Omit<TransportIo, "now" | "log" | "fs">;
|
|
76
|
+
testMode: boolean;
|
|
77
|
+
approverEnabled: boolean;
|
|
78
|
+
delay?: (ms: number) => Promise<void>;
|
|
79
|
+
guardBaseUrls: {
|
|
80
|
+
trust: string | null;
|
|
81
|
+
safety: string | null;
|
|
82
|
+
};
|
|
83
|
+
via: string | null;
|
|
84
|
+
};
|
|
85
|
+
export declare class PayfetchEngine {
|
|
86
|
+
#private;
|
|
87
|
+
guards: PrePayGuard[];
|
|
88
|
+
constructor(cfg: EngineConfig);
|
|
89
|
+
private newReceiptId;
|
|
90
|
+
private capsOf;
|
|
91
|
+
private transportIo;
|
|
92
|
+
fetch(url: string, init?: RequestInit, opts?: FetchOpts): Promise<PayfetchFetchResult>;
|
|
93
|
+
quote(url: string, init?: RequestInit): Promise<{
|
|
94
|
+
decision: Decision;
|
|
95
|
+
receipt: Receipt;
|
|
96
|
+
}>;
|
|
97
|
+
status(): Promise<SpendStatus>;
|
|
98
|
+
receipts(q: {
|
|
99
|
+
sinceTs?: number;
|
|
100
|
+
host?: string;
|
|
101
|
+
outcome?: Outcome;
|
|
102
|
+
limit?: number;
|
|
103
|
+
}): Receipt[];
|
|
104
|
+
listApprovals(): ReturnType<Budget["listApprovals"]>;
|
|
105
|
+
queueCapableNow(): boolean;
|
|
106
|
+
resolveApproval(approvalId: string, approve: boolean): {
|
|
107
|
+
ok: boolean;
|
|
108
|
+
error?: string;
|
|
109
|
+
};
|
|
110
|
+
private run;
|
|
111
|
+
private executePayment;
|
|
112
|
+
private runGuard;
|
|
113
|
+
private guardUnavailableResult;
|
|
114
|
+
private runApproval;
|
|
115
|
+
private elicitUnavailable;
|
|
116
|
+
private queueApproval;
|
|
117
|
+
liveGuardBudgetUsd(id: GuardId): number;
|
|
118
|
+
makeGuardFetch(id: GuardId, budgetUsd: number | (() => number), baseUrl: string | null): GuardRuntime["guardFetch"];
|
|
119
|
+
private payGuardFetch;
|
|
120
|
+
private detectPayer;
|
|
121
|
+
private quoteAndReject;
|
|
122
|
+
private httpBlock;
|
|
123
|
+
private materialize;
|
|
124
|
+
private deny;
|
|
125
|
+
private denyGuardBlocked;
|
|
126
|
+
private baseReceipt;
|
|
127
|
+
private finish;
|
|
128
|
+
private decisionFromReceipt;
|
|
129
|
+
}
|
|
130
|
+
export type TerminalClass = {
|
|
131
|
+
outcome: Outcome;
|
|
132
|
+
holdDisposition: "settle" | "keep";
|
|
133
|
+
settlementConfirmed: boolean;
|
|
134
|
+
settledAmountUsd: number | null;
|
|
135
|
+
txRef: string | null;
|
|
136
|
+
note: string | null;
|
|
137
|
+
};
|
|
138
|
+
export declare function classifyTerminal(retry: TransportResult, quote: PaymentQuote): TerminalClass;
|
|
139
|
+
export declare function classifyFromParts(status: number, settlement: {
|
|
140
|
+
success: boolean;
|
|
141
|
+
transaction?: string;
|
|
142
|
+
} | null, _quote: PaymentQuote): TerminalClass;
|
|
143
|
+
export declare function stripReservedHeaders(headers: RequestInit["headers"]): Record<string, string>;
|