@dvmkit/sdk 0.1.5-rc.9 → 0.2.1-rc.9
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/README.md +14 -0
- package/dist/{chunk-MLRCSJYX.js → chunk-2D75MNNG.js} +178 -550
- package/dist/{chunk-BIFLRKMO.js → chunk-2KGG5ORW.js} +1 -1
- package/dist/{chunk-27V2ILSR.js → chunk-4A2RAKCW.js} +2 -2
- package/dist/{chunk-5URG56JJ.js → chunk-A4CM2BD3.js} +18 -2
- package/dist/{chunk-L4OYF4DQ.js → chunk-AJNFXR7J.js} +2 -2
- package/dist/{chunk-S3XAHZQY.js → chunk-COYKJRMM.js} +24 -3
- package/dist/{chunk-UP2F5RRT.js → chunk-D7YQYLLM.js} +1 -4
- package/dist/{chunk-CEOAHV2I.js → chunk-FDKRXOZO.js} +0 -5
- package/dist/{chunk-TQWGQCNV.js → chunk-FPOLJRBF.js} +5 -5
- package/dist/{chunk-BTZY7VPH.js → chunk-GAIPXGM3.js} +1 -1
- package/dist/{chunk-SSSZUVWM.js → chunk-JAHRF5X3.js} +89 -39
- package/dist/{chunk-5PBOA25N.js → chunk-JNKQZVZX.js} +472 -617
- package/dist/{chunk-U6M3ATSG.js → chunk-MD3H3BF7.js} +40 -6
- package/dist/{chunk-CGKZDODG.js → chunk-MG67KXU7.js} +0 -5
- package/dist/{chunk-KSQEFVFJ.js → chunk-MMCE7XSR.js} +1 -1
- package/dist/{chunk-JZWELPFH.js → chunk-N5B66UOS.js} +4 -16
- package/dist/{chunk-BIP6G74V.js → chunk-O6D72KYA.js} +10 -10
- package/dist/{chunk-KXCKFIJI.js → chunk-QX52F6IB.js} +1 -1
- package/dist/{chunk-RW5LP57K.js → chunk-SDK6KDJN.js} +0 -1
- package/dist/{chunk-KVEHHC7W.js → chunk-U5VNEALI.js} +24 -13
- package/dist/{chunk-FROTD5XQ.js → chunk-V5ZRATJH.js} +2 -3
- package/dist/{chunk-EVBK675R.js → chunk-VA7GT6C6.js} +31 -37
- package/dist/{chunk-2UUXIIOC.js → chunk-VIVXB4CD.js} +3 -3
- package/dist/{chunk-6BQM7TOW.js → chunk-XMDQOXHO.js} +11 -38
- package/dist/{chunk-DMNLFNTW.js → chunk-XRLKGCAW.js} +2 -2
- package/dist/{chunk-M7LHFJ5K.js → chunk-XZ7LW4S5.js} +2 -3
- package/dist/{chunk-E4EVGPDX.js → chunk-YO56VCTQ.js} +0 -6
- package/dist/{credit-ledger-2DFQHNLB.js → credit-ledger-MHA652TV.js} +2 -2
- package/dist/{credit-menu-enwMbn55.d.ts → credit-menu-kDA9RVzD.d.ts} +524 -1077
- package/dist/{fx-D860pZvP.d.ts → fx-DhyT1Cjh.d.ts} +38 -82
- package/dist/index.d.ts +11 -26
- package/dist/index.js +2 -2
- package/dist/internal/caller.d.ts +644 -2140
- package/dist/internal/caller.js +32 -66
- package/dist/internal/server.d.ts +40 -90
- package/dist/internal/server.js +16 -16
- package/dist/{job-store-BUGqvCfL.d.ts → job-store-D3CcZp_g.d.ts} +73 -66
- package/dist/{lightning-backend-BozcevPZ.d.ts → lightning-backend-BhsyrBLa.d.ts} +19 -41
- package/dist/{memory-credit-ledger-MNUOTQO5.js → memory-credit-ledger-BYBATRN3.js} +3 -3
- package/dist/{mpp-secret-state-WNAQQ6K4.js → mpp-secret-state-B5GCILVH.js} +1 -1
- package/dist/{mpp-setup-4FJD6ZHV.js → mpp-setup-MPP3ST5X.js} +1 -1
- package/dist/{payout-reporter-RG6XNGPI.js → payout-reporter-KEMUXEUO.js} +2 -2
- package/dist/{postgres-consumed-credential-store-VHBT4KEA.js → postgres-consumed-credential-store-6N2V5MJN.js} +2 -2
- package/dist/postgres-job-store-MZZZMS2B.js +7 -0
- package/dist/postgres-kv-store-DXGTKXLU.js +7 -0
- package/dist/{postgres-replay-store-UJXRT6VO.js → postgres-replay-store-ZF4JNZOW.js} +2 -2
- package/dist/{pricing-4CEB34RM.js → pricing-MU5GNUJZ.js} +1 -1
- package/dist/{processed-payment-store-HAA4SFNK.js → processed-payment-store-GKMAWOM3.js} +2 -2
- package/dist/revenue-reporter-O75BGYFK.js +7 -0
- package/dist/server/index.d.ts +47 -159
- package/dist/server/index.js +67 -138
- package/dist/{ssrf-DbFkpDv0.d.ts → ssrf-dMooihtY.d.ts} +1 -2
- package/dist/{step-cache-5dljDqrQ.d.ts → step-cache-v2yhyhSZ.d.ts} +617 -1532
- package/dist/{tempo-charge-store-RIFTALZK.js → tempo-charge-store-SDTUNTKA.js} +2 -2
- package/dist/{tempo-lifecycle-DFIXQ54Q.js → tempo-lifecycle-G5SB4HFC.js} +5 -5
- package/dist/{tempo-session-store-2JNOKJGX.js → tempo-session-store-WF3VZXNF.js} +2 -2
- package/dist/{tempo-wallet-4QKSV65O.js → tempo-wallet-ZFNUDFVO.js} +2 -2
- package/dist/testing/index.d.ts +5 -15
- package/dist/testing/index.js +4 -11
- package/dist/{usd-DoRuAckA.d.ts → usd-Cu9G6xv2.d.ts} +14 -16
- package/dist/{wallet-CJC8lwxx.d.ts → wallet-Dwjs5n_M.d.ts} +1 -1
- package/dist/{x402-5H27DCBE.js → x402-CI6PSHNO.js} +5 -5
- package/package.json +3 -2
- package/dist/postgres-job-store-3RAXMNSY.js +0 -7
- package/dist/postgres-kv-store-JFBDP5IP.js +0 -7
- package/dist/revenue-reporter-ASZ7SHHH.js +0 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
StepCache,
|
|
3
3
|
loggers
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-YO56VCTQ.js";
|
|
5
5
|
import {
|
|
6
6
|
redactUrl
|
|
7
7
|
} from "./chunk-FUJ36YDV.js";
|
|
@@ -23,6 +23,11 @@ function toJobRecord(job) {
|
|
|
23
23
|
authRequestPath: job.authRequestPath,
|
|
24
24
|
status: job.status,
|
|
25
25
|
summary: job.summary,
|
|
26
|
+
timingKnown: job.timingKnown,
|
|
27
|
+
endedBy: job.endedBy,
|
|
28
|
+
terminalAtMs: job.terminalAtMs,
|
|
29
|
+
callerWaitMs: job.callerWaitMs,
|
|
30
|
+
awaitingInputAtMs: job.awaitingInputAtMs,
|
|
26
31
|
messages: job.messages,
|
|
27
32
|
seq: job.seq,
|
|
28
33
|
paidMsats: job.paidMsats,
|
|
@@ -69,6 +74,11 @@ function fromJobRecord(record) {
|
|
|
69
74
|
requesterTokenHash: record.requesterTokenHash,
|
|
70
75
|
status: record.status,
|
|
71
76
|
summary: record.summary,
|
|
77
|
+
timingKnown: record.timingKnown,
|
|
78
|
+
endedBy: record.endedBy,
|
|
79
|
+
terminalAtMs: record.terminalAtMs,
|
|
80
|
+
callerWaitMs: record.callerWaitMs,
|
|
81
|
+
awaitingInputAtMs: record.awaitingInputAtMs,
|
|
72
82
|
messages: [...record.messages],
|
|
73
83
|
seq: record.seq,
|
|
74
84
|
paidMsats: record.paidMsats,
|
|
@@ -138,11 +148,12 @@ function providerMessage(job, type, content) {
|
|
|
138
148
|
const pendingPaymentDelta = typeof paymentRequest.amount_msats === "number" ? paymentRequest.amount_msats : void 0;
|
|
139
149
|
const pendingMppChallengeIds = paymentRequest.tempo?.challenges?.map((challenge) => challenge.id).filter((id) => typeof id === "string");
|
|
140
150
|
job.messageAppender?.(msg, {
|
|
151
|
+
awaitingInput: job.status === "awaiting-input",
|
|
141
152
|
pendingPaymentDelta,
|
|
142
153
|
pendingMppChallengeIds,
|
|
143
154
|
pendingX402Nonce: typeof paymentRequest.x402?.nonce === "string" ? paymentRequest.x402.nonce : void 0,
|
|
144
155
|
pendingX402AmountUsdcMicro: typeof paymentRequest.x402?.required_usdc_micro === "string" ? paymentRequest.x402.required_usdc_micro : void 0,
|
|
145
|
-
//
|
|
156
|
+
// read off the job rather than the content — the fiat envelope
|
|
146
157
|
// is ledger state, not part of the wire shape, and `requestPayment` has
|
|
147
158
|
// already pinned it on the job by the time this fires (same ordering the
|
|
148
159
|
// `pendingPaymentMsats` comment above describes).
|
|
@@ -151,7 +162,7 @@ function providerMessage(job, type, content) {
|
|
|
151
162
|
});
|
|
152
163
|
return;
|
|
153
164
|
}
|
|
154
|
-
job.messageAppender?.(msg);
|
|
165
|
+
job.messageAppender?.(msg, type === "prompt" ? { awaitingInput: job.status === "awaiting-input" } : void 0);
|
|
155
166
|
}
|
|
156
167
|
function localMessage(job, from, type, content) {
|
|
157
168
|
job.seq++;
|
|
@@ -215,9 +226,7 @@ var MemoryJobStore = class {
|
|
|
215
226
|
notifySubscribers = /* @__PURE__ */ new Map();
|
|
216
227
|
reactivationLocks = /* @__PURE__ */ new Set();
|
|
217
228
|
requestIdClaims = /* @__PURE__ */ new Map();
|
|
218
|
-
/** Per-DVM monotonic receipt counter (internal-review) — the `receipt_counter` row. */
|
|
219
229
|
receiptCounter = 0;
|
|
220
|
-
/** Receipt sequence already allocated to a job, keyed by job id. */
|
|
221
230
|
receiptSeqs = /* @__PURE__ */ new Map();
|
|
222
231
|
get(id) {
|
|
223
232
|
const record = this.data.get(id);
|
|
@@ -280,7 +289,10 @@ var MemoryJobStore = class {
|
|
|
280
289
|
return Promise.resolve();
|
|
281
290
|
}
|
|
282
291
|
const snapshot = structuredClone(record);
|
|
292
|
+
if (existing) snapshot.createdAt = existing.createdAt;
|
|
283
293
|
if (existing?.receipt) snapshot.receipt = existing.receipt;
|
|
294
|
+
snapshot.timingKnown = existing ? existing.timingKnown ?? false : record.timingKnown ?? true;
|
|
295
|
+
applyTiming(snapshot, existing, Date.now());
|
|
284
296
|
this.data.set(record.id, snapshot);
|
|
285
297
|
const cur = this.nextSeq.get(record.id) ?? 1;
|
|
286
298
|
this.nextSeq.set(record.id, Math.max(cur, record.seq + 1));
|
|
@@ -351,6 +363,10 @@ var MemoryJobStore = class {
|
|
|
351
363
|
}
|
|
352
364
|
const seq = this.allocateSeq(jobId);
|
|
353
365
|
const fullMessage = { ...message, seq };
|
|
366
|
+
if (opts?.awaitingInput && !isTerminal(record.status)) {
|
|
367
|
+
applyTiming(record, structuredClone(record), Date.now(), "awaiting-input");
|
|
368
|
+
record.status = "awaiting-input";
|
|
369
|
+
}
|
|
354
370
|
this.messagesFor(jobId).push({ message: fullMessage, status: "verified" });
|
|
355
371
|
if (opts?.pendingPaymentDelta !== void 0) {
|
|
356
372
|
if (opts.pendingPaymentDelta !== 0) {
|
|
@@ -503,6 +519,7 @@ var MemoryJobStore = class {
|
|
|
503
519
|
claimForProcessing(jobId) {
|
|
504
520
|
const record = this.data.get(jobId);
|
|
505
521
|
if (record?.status !== "awaiting-input") return Promise.resolve(false);
|
|
522
|
+
applyTiming(record, structuredClone(record), Date.now(), "processing");
|
|
506
523
|
record.status = "processing";
|
|
507
524
|
record.lastActivityAt = Date.now();
|
|
508
525
|
return Promise.resolve(true);
|
|
@@ -565,7 +582,6 @@ var MemoryJobStore = class {
|
|
|
565
582
|
this.notifySubscribers.clear();
|
|
566
583
|
return Promise.resolve();
|
|
567
584
|
}
|
|
568
|
-
// ── Internal helpers ──────────────────────────────────────────────────
|
|
569
585
|
/**
|
|
570
586
|
* Shared body of `cancelStaleJob` (activity cutoff supplied) and `cancelJob`
|
|
571
587
|
* (`null` cutoff — unconditional). Mirrors `PostgresJobStore.terminateJob`:
|
|
@@ -586,6 +602,8 @@ var MemoryJobStore = class {
|
|
|
586
602
|
content: { reason }
|
|
587
603
|
};
|
|
588
604
|
this.messagesFor(jobId).push({ message: cancelMsg, status: "verified" });
|
|
605
|
+
record.endedBy = expectedActivityBefore === null || record.status === "awaiting-input" ? "caller" : "provider";
|
|
606
|
+
applyTiming(record, structuredClone(record), Date.now(), terminalStatus);
|
|
589
607
|
record.status = terminalStatus;
|
|
590
608
|
record.summary = record.summary ?? reason;
|
|
591
609
|
record.messages = [...record.messages, cancelMsg];
|
|
@@ -645,6 +663,22 @@ function snapshotCounters(record) {
|
|
|
645
663
|
summary: record.summary
|
|
646
664
|
};
|
|
647
665
|
}
|
|
666
|
+
function applyTiming(next, prior, now, status = next.status) {
|
|
667
|
+
if (prior && isTerminal(prior.status)) {
|
|
668
|
+
next.endedBy = prior.endedBy;
|
|
669
|
+
next.terminalAtMs = prior.terminalAtMs;
|
|
670
|
+
next.callerWaitMs = prior.callerWaitMs;
|
|
671
|
+
next.awaitingInputAtMs = void 0;
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
const start = prior?.awaitingInputAtMs ?? (prior?.status === "awaiting-input" ? prior.lastActivityAt : void 0);
|
|
675
|
+
next.callerWaitMs = (prior?.callerWaitMs ?? next.callerWaitMs ?? 0) + (start !== void 0 && status !== "awaiting-input" ? Math.max(0, now - start) : 0);
|
|
676
|
+
next.awaitingInputAtMs = status === "awaiting-input" ? start ?? now : void 0;
|
|
677
|
+
if (isTerminal(status)) {
|
|
678
|
+
next.terminalAtMs = now;
|
|
679
|
+
next.endedBy = status === "completed" ? void 0 : next.endedBy ?? "provider";
|
|
680
|
+
}
|
|
681
|
+
}
|
|
648
682
|
|
|
649
683
|
// src/sdk/server/instrumented-fetch.ts
|
|
650
684
|
function createInstrumentedFetch(signal) {
|
|
@@ -30,7 +30,6 @@ var PROTECTED_CONFIG_FIELDS = ["nwcUri", "defaultIdentity", "tempo", "x402"];
|
|
|
30
30
|
var ProtectedFieldAuthorization = class {
|
|
31
31
|
/** Targets this authorization covers. A field also covers its own leaves. */
|
|
32
32
|
targets;
|
|
33
|
-
/** Prefer {@link authorizeProtectedRemoval}; this exists for it to call. */
|
|
34
33
|
constructor(targets) {
|
|
35
34
|
this.targets = [...targets];
|
|
36
35
|
}
|
|
@@ -304,11 +303,7 @@ function protectedRemovalError(targets, reason) {
|
|
|
304
303
|
}
|
|
305
304
|
var PROTECTED_FIELDS = {
|
|
306
305
|
nwcUri: { aliases: [], credentialLeaves: [], secret: true, hexCredential: false },
|
|
307
|
-
// A pointer at a key, not a key: worth protecting from removal, and never
|
|
308
|
-
// worth stripping out of a stored copy — that is recovery lost for nothing.
|
|
309
306
|
defaultIdentity: { aliases: [], credentialLeaves: [], secret: false, hexCredential: false },
|
|
310
|
-
// `mpp` is the pre-internal-review name; a caller who has not run a mutating command
|
|
311
|
-
// since the rename still has their private key under it.
|
|
312
307
|
tempo: { aliases: ["mpp"], credentialLeaves: ["apiKey"], secret: true, hexCredential: true },
|
|
313
308
|
x402: { aliases: [], credentialLeaves: ["privateKey"], secret: true, hexCredential: true }
|
|
314
309
|
};
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-YG7G4DPZ.js";
|
|
5
5
|
import {
|
|
6
6
|
withSdkInitLock
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-COYKJRMM.js";
|
|
8
8
|
|
|
9
9
|
// src/sdk/server/tempo-charge-store.ts
|
|
10
10
|
var DEFAULT_TABLE_NAME = "mpp_tempo_charge_replays";
|
|
@@ -46,7 +46,6 @@ var PostgresTempoChargeStore = class {
|
|
|
46
46
|
);
|
|
47
47
|
return rows[0] ? parseStoreValue(rows[0].value_json) : null;
|
|
48
48
|
}
|
|
49
|
-
/** Replace one stored value. */
|
|
50
49
|
async put(key, value) {
|
|
51
50
|
await this.update(key, () => ({ op: "set", value, result: void 0 }));
|
|
52
51
|
}
|
|
@@ -55,19 +54,9 @@ var PostgresTempoChargeStore = class {
|
|
|
55
54
|
await this.update(key, () => ({ op: "delete", result: void 0 }));
|
|
56
55
|
}
|
|
57
56
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* write is the one that matters here: `FOR UPDATE` locks nothing when no row
|
|
62
|
-
* exists, so two machines marking one hash for the first time would both read
|
|
63
|
-
* `null` and both believe they won.
|
|
64
|
-
*
|
|
65
|
-
* Replay keys are unique per credential, so they never contend. The contended
|
|
66
|
-
* key is the fee-sponsor budget — one per `(chain, sponsor)`, read-modified
|
|
67
|
-
* several times per sponsored charge — and serializing it across machines is
|
|
68
|
-
* the point of sharing the store at all. The critical section is a SELECT and
|
|
69
|
-
* an upsert, so a queue of waiters clears in tens of milliseconds; a pooled
|
|
70
|
-
* connection is held for exactly that long and never across a chain call.
|
|
57
|
+
* Advisory locking covers the absent-row first write before the row lock.
|
|
58
|
+
* Both competing presentations of one credential and the shared sponsor
|
|
59
|
+
* budget need this serialization. The transaction contains no chain call.
|
|
71
60
|
*/
|
|
72
61
|
async update(key, fn) {
|
|
73
62
|
const client = await this.pool.connect();
|
|
@@ -106,7 +95,6 @@ var PostgresTempoChargeStore = class {
|
|
|
106
95
|
client.release();
|
|
107
96
|
}
|
|
108
97
|
}
|
|
109
|
-
/** The boot DDL itself — always runs under {@link withSdkInitLock} (internal-review). */
|
|
110
98
|
async createTables() {
|
|
111
99
|
await this.pool.query(`
|
|
112
100
|
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
listCredits,
|
|
5
5
|
maxAdvertisedMicro,
|
|
6
6
|
withinAdvertisedTolerance
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-MMCE7XSR.js";
|
|
8
8
|
import {
|
|
9
9
|
authorizeProtectedRemoval,
|
|
10
10
|
isValidEvmPrivateKey,
|
|
11
11
|
loadConfig,
|
|
12
12
|
updateConfig
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-MG67KXU7.js";
|
|
14
14
|
import {
|
|
15
15
|
X402_DEFAULT_NETWORK,
|
|
16
16
|
X402_V1_VERSION,
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
getLastKnownBtcRate,
|
|
35
35
|
msatsToUsdc,
|
|
36
36
|
usdcToMsats
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-A4CM2BD3.js";
|
|
38
38
|
import {
|
|
39
39
|
DvmError,
|
|
40
40
|
X402_CHANNELS_FILE,
|
|
@@ -190,7 +190,7 @@ function x402PendingFundingJson(entry) {
|
|
|
190
190
|
amount_micro: entry.amountMicro,
|
|
191
191
|
// What the ERC-3009 authorization can still pull, which is the figure a
|
|
192
192
|
// `--force` retirement is really deciding about: a deposit's authorized
|
|
193
|
-
// value can exceed the amount funded
|
|
193
|
+
// value can exceed the amount funded, so `amount_micro` alone
|
|
194
194
|
// understates it. Null on a voucher-only payload, which authorizes no
|
|
195
195
|
// deposit at all.
|
|
196
196
|
deposit_micro: entry.depositMicro ?? null,
|
|
@@ -243,7 +243,7 @@ var DvmX402ChannelStorage = class {
|
|
|
243
243
|
/**
|
|
244
244
|
* Upstream's ordinary write. Its chain-recovery paths are authoritative and
|
|
245
245
|
* clear the unreconciled stamp. DVM-response paths scope this same write to
|
|
246
|
-
* persist their weaker provenance atomically with the balance
|
|
246
|
+
* persist their weaker provenance atomically with the balance.
|
|
247
247
|
*/
|
|
248
248
|
set(key, context) {
|
|
249
249
|
return fromSynchronousStore(() => {
|
|
@@ -266,7 +266,7 @@ var DvmX402ChannelStorage = class {
|
|
|
266
266
|
}
|
|
267
267
|
/**
|
|
268
268
|
* Write a context whose `balance` carries a deposit this machine added on its
|
|
269
|
-
* own authority, and stamp the record as unreconciled
|
|
269
|
+
* own authority, and stamp the record as unreconciled.
|
|
270
270
|
*
|
|
271
271
|
* Separate from {@link set} because the two differ in exactly one thing that
|
|
272
272
|
* matters later: where the figure came from. Nothing else about the write
|
|
@@ -279,7 +279,7 @@ var DvmX402ChannelStorage = class {
|
|
|
279
279
|
}
|
|
280
280
|
/**
|
|
281
281
|
* Write a context whose `balance` is byte-for-byte the one already stored,
|
|
282
|
-
* leaving the unreconciled stamp exactly as it found it
|
|
282
|
+
* leaving the unreconciled stamp exactly as it found it.
|
|
283
283
|
*
|
|
284
284
|
* The replay acknowledgement's idempotent second pass is what needs this: it
|
|
285
285
|
* restates a cumulative base without adding to the balance, so it observes
|
|
@@ -294,7 +294,7 @@ var DvmX402ChannelStorage = class {
|
|
|
294
294
|
}
|
|
295
295
|
/**
|
|
296
296
|
* Seed upstream's cooperative-refund probe without changing the acknowledged
|
|
297
|
-
* base. Records written before
|
|
297
|
+
* base. Records written before have only the upstream context, so
|
|
298
298
|
* `hold` adopts its acknowledged value rather than losing that base to the
|
|
299
299
|
* chain floor this seeds. The seed itself is a `channels()` read, so it is
|
|
300
300
|
* `observed` and settles the unreconciled question like any other.
|
|
@@ -605,7 +605,7 @@ function loadFile() {
|
|
|
605
605
|
channels: raw.channels,
|
|
606
606
|
pending: raw.pending,
|
|
607
607
|
// Tolerated rather than required, unlike its two siblings: every file
|
|
608
|
-
// written before
|
|
608
|
+
// written before lacks the key, and failing closed on it would
|
|
609
609
|
// read as a corrupt journal on a store that is perfectly intact.
|
|
610
610
|
exact: Array.isArray(raw.exact) ? raw.exact : []
|
|
611
611
|
};
|
|
@@ -753,7 +753,7 @@ async function getX402Balance(wallet) {
|
|
|
753
753
|
usdc: Number(balance) / 1e6,
|
|
754
754
|
network: w.network,
|
|
755
755
|
tokenAddress: spec.usdc,
|
|
756
|
-
//
|
|
756
|
+
// redacted here rather than at a display surface, so the clear
|
|
757
757
|
// endpoint never leaves this function on the success path either.
|
|
758
758
|
rpcUrlRedacted: rpcUrl ? redactUrl(rpcUrl) : void 0,
|
|
759
759
|
rpcSource
|
|
@@ -15,14 +15,14 @@ import {
|
|
|
15
15
|
lotOwedSats,
|
|
16
16
|
netOwedSats,
|
|
17
17
|
x402SettlementPending
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-2D75MNNG.js";
|
|
19
19
|
|
|
20
20
|
// src/sdk/server/memory-credit-ledger.ts
|
|
21
21
|
import { randomUUID } from "crypto";
|
|
22
22
|
var MemoryCreditLedger = class _MemoryCreditLedger {
|
|
23
23
|
/**
|
|
24
24
|
* Per-process and lost on restart, so a DVM backed by this ledger never
|
|
25
|
-
* advertises the funding menu outside devMode
|
|
25
|
+
* advertises the funding menu outside devMode — funding on one
|
|
26
26
|
* machine and drawing on another would `credit_not_found`.
|
|
27
27
|
*/
|
|
28
28
|
durable = false;
|
|
@@ -42,7 +42,6 @@ var MemoryCreditLedger = class _MemoryCreditLedger {
|
|
|
42
42
|
fork.x402Settlements = this.x402Settlements;
|
|
43
43
|
return fork;
|
|
44
44
|
}
|
|
45
|
-
/** See {@link CreditLedger.useX402SettlementGate} — same contract (internal-review). */
|
|
46
45
|
useX402SettlementGate(gate) {
|
|
47
46
|
this.x402Settlements = gate;
|
|
48
47
|
}
|
|
@@ -62,12 +61,10 @@ var MemoryCreditLedger = class _MemoryCreditLedger {
|
|
|
62
61
|
);
|
|
63
62
|
return run(() => this.drawSync(args, blocking));
|
|
64
63
|
}
|
|
65
|
-
/** See {@link CreditLedger.blockingX402Refund} — same contract (internal-review). */
|
|
66
64
|
async blockingX402Refund(channelId, statuses) {
|
|
67
65
|
if (!channelId || !this.x402Settlements) return void 0;
|
|
68
66
|
return this.x402Settlements.pendingRefundSettlement(channelId, statuses);
|
|
69
67
|
}
|
|
70
|
-
/** The unresolved x402 refund that must stop an effect on `creditId` (internal-review). */
|
|
71
68
|
blockingRefundForCredit(creditId, statuses) {
|
|
72
69
|
return this.blockingX402Refund(this.credits.get(creditId)?.x402ChannelId, statuses);
|
|
73
70
|
}
|
|
@@ -540,7 +537,6 @@ var MemoryCreditLedger = class _MemoryCreditLedger {
|
|
|
540
537
|
credit.draws.set(args.drawId, draw);
|
|
541
538
|
return { ...drawResult(draw, credit), replayed: false };
|
|
542
539
|
}
|
|
543
|
-
/** Mid-job top-up growth (internal-review) — see `CreditLedger.growDraw` for the semantics. */
|
|
544
540
|
growDrawSync(args) {
|
|
545
541
|
assertAmount(args.addAmountMicro, { min: 1 });
|
|
546
542
|
const credit = this.requireCredit(args.creditId);
|
|
@@ -667,7 +663,6 @@ var MemoryCreditLedger = class _MemoryCreditLedger {
|
|
|
667
663
|
const page = after ? out.filter((d) => compareStaleDrawKey(d, after) > 0) : out;
|
|
668
664
|
return Promise.resolve(page.slice(0, args.limit));
|
|
669
665
|
}
|
|
670
|
-
/** Settled draws' native value for the credit an x402 channel funded (internal-review). */
|
|
671
666
|
earnedNativeForX402Channel(channelId) {
|
|
672
667
|
for (const credit of this.credits.values()) {
|
|
673
668
|
if (credit.x402ChannelId !== channelId) continue;
|
|
@@ -757,7 +752,6 @@ var MemoryCreditLedger = class _MemoryCreditLedger {
|
|
|
757
752
|
[...this.x402Losses.values()].sort((a, b) => b.observedAt - a.observedAt || a.creditId.localeCompare(b.creditId)).slice(0, args?.limit ?? 50)
|
|
758
753
|
);
|
|
759
754
|
}
|
|
760
|
-
// ── Drains (internal-review) — semantics mirror the Postgres ledger ─────────
|
|
761
755
|
async requestDrain(args) {
|
|
762
756
|
const wedged = await this.blockingRefundForCredit(
|
|
763
757
|
args.creditId,
|
|
@@ -861,15 +855,17 @@ var MemoryCreditLedger = class _MemoryCreditLedger {
|
|
|
861
855
|
bitcoinDepositLiability() {
|
|
862
856
|
const byRail = /* @__PURE__ */ new Map();
|
|
863
857
|
const byCurrency = /* @__PURE__ */ new Map();
|
|
858
|
+
const uncoveredByCurrency = /* @__PURE__ */ new Map();
|
|
859
|
+
const unbackedByCurrency = /* @__PURE__ */ new Map();
|
|
864
860
|
const liability = {
|
|
865
861
|
depositSats: 0,
|
|
866
862
|
credits: 0,
|
|
867
863
|
byRail: [],
|
|
868
864
|
byCurrency: [],
|
|
869
865
|
uncoveredCredits: 0,
|
|
870
|
-
|
|
866
|
+
uncoveredByCurrency: [],
|
|
871
867
|
unbackedCredits: 0,
|
|
872
|
-
|
|
868
|
+
unbackedByCurrency: []
|
|
873
869
|
};
|
|
874
870
|
for (const credit of this.credits.values()) {
|
|
875
871
|
if (!isNonChannelBitcoinRail(credit.rail)) continue;
|
|
@@ -887,10 +883,24 @@ var MemoryCreditLedger = class _MemoryCreditLedger {
|
|
|
887
883
|
liability.credits++;
|
|
888
884
|
if (covered < credit.balanceMicro) {
|
|
889
885
|
liability.uncoveredCredits++;
|
|
890
|
-
|
|
886
|
+
const currency2 = uncoveredByCurrency.get(credit.currency) ?? {
|
|
887
|
+
currency: credit.currency,
|
|
888
|
+
micro: 0,
|
|
889
|
+
credits: 0
|
|
890
|
+
};
|
|
891
|
+
currency2.micro += credit.balanceMicro;
|
|
892
|
+
currency2.credits++;
|
|
893
|
+
uncoveredByCurrency.set(credit.currency, currency2);
|
|
891
894
|
} else if (basisless > 0) {
|
|
892
895
|
liability.unbackedCredits++;
|
|
893
|
-
|
|
896
|
+
const currency2 = unbackedByCurrency.get(credit.currency) ?? {
|
|
897
|
+
currency: credit.currency,
|
|
898
|
+
micro: 0,
|
|
899
|
+
credits: 0
|
|
900
|
+
};
|
|
901
|
+
currency2.micro += credit.balanceMicro;
|
|
902
|
+
currency2.credits++;
|
|
903
|
+
unbackedByCurrency.set(credit.currency, currency2);
|
|
894
904
|
}
|
|
895
905
|
const rail = byRail.get(credit.rail) ?? { rail: credit.rail, sats: 0, credits: 0 };
|
|
896
906
|
rail.sats += owed;
|
|
@@ -907,6 +917,8 @@ var MemoryCreditLedger = class _MemoryCreditLedger {
|
|
|
907
917
|
}
|
|
908
918
|
liability.byRail = [...byRail.values()];
|
|
909
919
|
liability.byCurrency = [...byCurrency.values()];
|
|
920
|
+
liability.uncoveredByCurrency = [...uncoveredByCurrency.values()];
|
|
921
|
+
liability.unbackedByCurrency = [...unbackedByCurrency.values()];
|
|
910
922
|
return Promise.resolve(liability);
|
|
911
923
|
}
|
|
912
924
|
listFundingLots(creditId) {
|
|
@@ -1046,7 +1058,6 @@ var MemoryCreditLedger = class _MemoryCreditLedger {
|
|
|
1046
1058
|
if (drain) drain.receipts.push(args.receipt);
|
|
1047
1059
|
});
|
|
1048
1060
|
}
|
|
1049
|
-
// ── Internals ─────────────────────────────────────────────────────────
|
|
1050
1061
|
requireDrain(creditId, drainId) {
|
|
1051
1062
|
const credit = this.credits.get(creditId);
|
|
1052
1063
|
const drain = credit?.drains.get(drainId);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
withSdkInitLock
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-COYKJRMM.js";
|
|
4
4
|
|
|
5
5
|
// src/sdk/server/postgres-kv-store.ts
|
|
6
6
|
var PostgresKVStore = class {
|
|
@@ -12,12 +12,11 @@ var PostgresKVStore = class {
|
|
|
12
12
|
async init() {
|
|
13
13
|
await withSdkInitLock(this.pool, () => this.createTables());
|
|
14
14
|
}
|
|
15
|
-
/** The boot DDL itself — always runs under {@link withSdkInitLock} (internal-review). */
|
|
16
15
|
async createTables() {
|
|
17
16
|
await this.pool.query(`
|
|
18
17
|
-- Single-tenant KV table aligned with the platform's isolate storage schema.
|
|
19
18
|
-- The only structural difference is the absence of dvm_id (SDK is single-DVM-per-DB).
|
|
20
|
-
--
|
|
19
|
+
-- Keep column additions aligned with the platform isolate schema.
|
|
21
20
|
CREATE TABLE IF NOT EXISTS kv_store (
|
|
22
21
|
key TEXT PRIMARY KEY,
|
|
23
22
|
value JSONB NOT NULL,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
withSdkInitLock
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-COYKJRMM.js";
|
|
4
4
|
|
|
5
5
|
// src/sdk/server/revenue-reporter.ts
|
|
6
6
|
import { randomUUID } from "crypto";
|
|
7
7
|
var ENDPOINTS = {
|
|
8
|
+
job_terminal: "/_internal/job-terminal",
|
|
8
9
|
revenue: "/_internal/job-revenue",
|
|
9
10
|
cost: "/_internal/job-cost",
|
|
10
11
|
deposit: "/_internal/credit-deposit",
|
|
@@ -51,7 +52,6 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
51
52
|
async init() {
|
|
52
53
|
await withSdkInitLock(this.db, () => this.createTables());
|
|
53
54
|
}
|
|
54
|
-
/** The boot DDL itself — always runs under {@link withSdkInitLock} (internal-review). */
|
|
55
55
|
async createTables() {
|
|
56
56
|
await this.db.query(`
|
|
57
57
|
CREATE TABLE IF NOT EXISTS ${PENDING_DVM_REPORTS_TABLE} (
|
|
@@ -65,6 +65,10 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
65
65
|
kind TEXT NOT NULL DEFAULT 'revenue'
|
|
66
66
|
)
|
|
67
67
|
`);
|
|
68
|
+
await this.db.query(`CREATE TABLE IF NOT EXISTS reported_job_terminals (
|
|
69
|
+
dvm_id TEXT NOT NULL, job_id TEXT NOT NULL, created_at BIGINT NOT NULL,
|
|
70
|
+
PRIMARY KEY (dvm_id, job_id)
|
|
71
|
+
)`);
|
|
68
72
|
await this.assertSdkTableShape(PENDING_DVM_REPORTS_TABLE);
|
|
69
73
|
await this.db.query(
|
|
70
74
|
`ALTER TABLE ${PENDING_DVM_REPORTS_TABLE} ADD COLUMN IF NOT EXISTS stale_reason TEXT`
|
|
@@ -74,7 +78,6 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
74
78
|
);
|
|
75
79
|
await this.migrateLegacySdkOutbox();
|
|
76
80
|
}
|
|
77
|
-
/** Copy rows from the old SDK-owned table without touching the platform's table of that name. */
|
|
78
81
|
async migrateLegacySdkOutbox() {
|
|
79
82
|
const columns = await this.tableColumns(LEGACY_PENDING_REPORTS_TABLE);
|
|
80
83
|
if (columns.size === 0) return;
|
|
@@ -106,7 +109,6 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
106
109
|
)
|
|
107
110
|
`);
|
|
108
111
|
}
|
|
109
|
-
/** Refuse a pre-existing relation whose columns cannot safely serve as the SDK outbox. */
|
|
110
112
|
async assertSdkTableShape(tableName) {
|
|
111
113
|
this.assertSdkColumns(tableName, await this.tableColumns(tableName));
|
|
112
114
|
}
|
|
@@ -130,6 +132,21 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
130
132
|
async report(payload) {
|
|
131
133
|
return this.enqueue("revenue", payload, payload.jobId);
|
|
132
134
|
}
|
|
135
|
+
/** The permanent deduplication claim and outbox insert commit together.
|
|
136
|
+
* The claim survives successful delivery, including for custom job stores.
|
|
137
|
+
*/
|
|
138
|
+
async reportTerminal(payload) {
|
|
139
|
+
const id = randomUUID();
|
|
140
|
+
const { rowCount } = await this.db.query(
|
|
141
|
+
`WITH claimed AS (
|
|
142
|
+
INSERT INTO reported_job_terminals (dvm_id, job_id, created_at)
|
|
143
|
+
VALUES ($5, $1, $4) ON CONFLICT DO NOTHING RETURNING job_id
|
|
144
|
+
) INSERT INTO ${PENDING_DVM_REPORTS_TABLE} (id, payload, created_at, kind)
|
|
145
|
+
SELECT $2, $3::jsonb, $4, 'job_terminal' FROM claimed`,
|
|
146
|
+
[payload.jobId, id, JSON.stringify(payload), Date.now(), payload.dvmId]
|
|
147
|
+
);
|
|
148
|
+
if (rowCount) void this.attemptDelivery(id, payload, "job_terminal", payload.jobId).catch(() => void 0);
|
|
149
|
+
}
|
|
133
150
|
/** Persist a zero-revenue job cost and attempt immediate delivery. */
|
|
134
151
|
async reportCost(payload) {
|
|
135
152
|
return this.enqueue("cost", payload, payload.jobId);
|
|
@@ -149,23 +166,15 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
149
166
|
await this.insertPending(tx, "release", payload);
|
|
150
167
|
}
|
|
151
168
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* Same discipline as {@link enqueueDeposit} and the same reason: no network
|
|
155
|
-
* I/O here, only the durable queue row, written through `tx` so the report
|
|
156
|
-
* and the status CAS commit or roll back together. A drain whose row was
|
|
157
|
-
* lost between the two would overstate outstanding liability forever —
|
|
158
|
-
* the mirror image of a lost deposit.
|
|
159
|
-
*
|
|
160
|
-
* Named for the payload rather than the verb because `drain` already means
|
|
161
|
-
* "flush the queue" in this class (see {@link drainPending}).
|
|
169
|
+
* Await durable queue insertion through tx so the terminal drain and its
|
|
170
|
+
* report commit or roll back together; no network I/O here.
|
|
162
171
|
*/
|
|
163
172
|
async enqueueCreditDrain(tx, payload) {
|
|
164
173
|
await this.insertPending(tx, "drain", payload);
|
|
165
174
|
}
|
|
166
175
|
/**
|
|
167
176
|
* Join a payout to the transaction that commits the movement it reports
|
|
168
|
-
*
|
|
177
|
+
* the batch close, the accumulator mark, the drain booking.
|
|
169
178
|
* No network I/O, only the durable queue row through `tx`, the discipline
|
|
170
179
|
* {@link enqueueCreditDrain} set and for the same reason: the money has
|
|
171
180
|
* already moved on a rail this ledger does not own, so the platform row is
|
|
@@ -187,7 +196,7 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
187
196
|
return this.enqueue("payout", payload, payload.payoutId);
|
|
188
197
|
}
|
|
189
198
|
/**
|
|
190
|
-
* Report one rail's pending-pool snapshot
|
|
199
|
+
* Report one rail's pending-pool snapshot. Best-effort and
|
|
191
200
|
* fire-and-forget, deliberately unlike {@link reportPayout}: a snapshot is
|
|
192
201
|
* replaced by the next tick, so queueing a stale one behind an outage would
|
|
193
202
|
* only deliver figures the platform already has newer ones for.
|
|
@@ -224,20 +233,14 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
224
233
|
}
|
|
225
234
|
}
|
|
226
235
|
/**
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
* Same discipline as {@link enqueueCreditDrain}: no network I/O, only the
|
|
231
|
-
* durable queue row written through `tx`. A release whose row was lost after
|
|
232
|
-
* the sweep committed would understate committed value forever; a lost
|
|
233
|
-
* reversal would overstate it, which is the worse direction — the builder
|
|
234
|
-
* would see money as theirs that a revived credit can still buy work with.
|
|
236
|
+
* Await durable queue insertion through tx so expiry release or reversal and
|
|
237
|
+
* its report commit together. A missing reversal would overstate builder value.
|
|
235
238
|
*/
|
|
236
239
|
async enqueueCreditExpiryRelease(tx, payload) {
|
|
237
240
|
await this.insertPending(tx, "expiry_release", payload);
|
|
238
241
|
}
|
|
239
242
|
/**
|
|
240
|
-
* Report a reaper-force-failed paid job to the platform
|
|
243
|
+
* Report a reaper-force-failed paid job to the platform so the
|
|
241
244
|
* operator receives an ambient notice that a DVM died or wedged mid-job.
|
|
242
245
|
* Best-effort and fire-and-forget: unlike {@link report} there is no local
|
|
243
246
|
* durable queue — a non-2xx or transport failure is structured-logged
|
|
@@ -277,7 +280,7 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
277
280
|
}
|
|
278
281
|
}
|
|
279
282
|
/**
|
|
280
|
-
* Report a settled draw whose revenue event was skipped
|
|
283
|
+
* Report a settled draw whose revenue event was skipped.
|
|
281
284
|
* Best-effort and fire-and-forget: the SDK's structured
|
|
282
285
|
* `revenue_skipped_no_rail` log is the durable backstop, so a failed notice
|
|
283
286
|
* must never wedge terminal handling or the orphan-draw reconciler.
|
|
@@ -317,7 +320,6 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
317
320
|
);
|
|
318
321
|
}
|
|
319
322
|
}
|
|
320
|
-
/** Report the self-relay's Base gas gauge for platform-side paging and recovery. */
|
|
321
323
|
async reportX402SelfRelayGasBalance(payload) {
|
|
322
324
|
const url = `${this.platformUrl}/_internal/x402-self-relay-gas-balance`;
|
|
323
325
|
try {
|
|
@@ -349,7 +351,6 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
349
351
|
);
|
|
350
352
|
}
|
|
351
353
|
}
|
|
352
|
-
/** Report redacted self-relay RPC health for platform-side paging and recovery. */
|
|
353
354
|
async reportX402SelfRelayRpcHealth(payload) {
|
|
354
355
|
const url = `${this.platformUrl}/_internal/x402-self-relay-rpc-health`;
|
|
355
356
|
try {
|
|
@@ -381,11 +382,9 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
381
382
|
);
|
|
382
383
|
}
|
|
383
384
|
}
|
|
384
|
-
/** Report the Tempo operator's fee-token gauge for paging and recovery. */
|
|
385
385
|
async reportTempoSettlementBalance(payload) {
|
|
386
386
|
await this.postTempoSettlementReadiness("balance", payload);
|
|
387
387
|
}
|
|
388
|
-
/** Report a redacted operator fee rejection immediately. */
|
|
389
388
|
async reportTempoSettlementFailure(payload) {
|
|
390
389
|
await this.postTempoSettlementReadiness("failure", payload);
|
|
391
390
|
}
|
|
@@ -422,14 +421,12 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
422
421
|
);
|
|
423
422
|
}
|
|
424
423
|
}
|
|
425
|
-
/** Start the background retry loop. */
|
|
426
424
|
startRetryLoop() {
|
|
427
425
|
if (this.retryTimer) return;
|
|
428
426
|
this.retryTimer = setInterval(() => {
|
|
429
427
|
void this.drainPending();
|
|
430
428
|
}, _RevenueReporter.RETRY_INTERVAL_MS);
|
|
431
429
|
}
|
|
432
|
-
/** Stop the background retry loop. */
|
|
433
430
|
stop() {
|
|
434
431
|
if (this.retryTimer) {
|
|
435
432
|
clearInterval(this.retryTimer);
|
|
@@ -466,8 +463,8 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
466
463
|
/**
|
|
467
464
|
* Deliver one bounded batch from the durable queue.
|
|
468
465
|
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
466
|
+
* Processes due, non-stale rows; failures back off up to five minutes and
|
|
467
|
+
* become terminal after 24 hours. Successful deliveries remove their rows.
|
|
471
468
|
*/
|
|
472
469
|
async drainPending() {
|
|
473
470
|
try {
|
|
@@ -517,13 +514,11 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
517
514
|
console.error("[revenue-reporter] Retry loop error:", err);
|
|
518
515
|
}
|
|
519
516
|
}
|
|
520
|
-
/** Durably queue one report of either kind, then try it once immediately. */
|
|
521
517
|
async enqueue(kind, payload, label) {
|
|
522
518
|
const id = await this.insertPending(this.db, kind, payload);
|
|
523
519
|
void this.attemptDelivery(id, payload, kind, label).catch(() => {
|
|
524
520
|
});
|
|
525
521
|
}
|
|
526
|
-
/** Insert one pending report through either the pool or an open transaction. */
|
|
527
522
|
async insertPending(q, kind, payload) {
|
|
528
523
|
const id = randomUUID();
|
|
529
524
|
await q.query(
|
|
@@ -575,7 +570,6 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
575
570
|
console.warn(`[revenue-reporter] delivery failed for ${id}: HTTP ${res.status} \u2014 will retry`);
|
|
576
571
|
throw new Error(`Platform returned ${res.status}`);
|
|
577
572
|
}
|
|
578
|
-
/** Mark a pending row terminal (stale) with a classification reason. */
|
|
579
573
|
async markStale(id, reason) {
|
|
580
574
|
await this.db.query(
|
|
581
575
|
`UPDATE ${PENDING_DVM_REPORTS_TABLE} SET stale = true, stale_reason = $1 WHERE id = $2`,
|