@dvmkit/sdk 0.1.5-rc.8 → 0.2.0-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 +12 -0
- package/dist/{chunk-BIP6G74V.js → chunk-2ATUAUAO.js} +8 -8
- package/dist/{chunk-27V2ILSR.js → chunk-4A2RAKCW.js} +2 -2
- package/dist/{chunk-EVBK675R.js → chunk-6GRIKOFB.js} +28 -23
- package/dist/{chunk-CEOAHV2I.js → chunk-FDKRXOZO.js} +0 -5
- package/dist/{chunk-L4OYF4DQ.js → chunk-FT6HTUM4.js} +1 -1
- package/dist/{chunk-BTZY7VPH.js → chunk-GAIPXGM3.js} +1 -1
- package/dist/{chunk-U6M3ATSG.js → chunk-JDT5LCJC.js} +40 -6
- package/dist/{chunk-FROTD5XQ.js → chunk-JLXYOV4Y.js} +1 -2
- package/dist/{chunk-M7LHFJ5K.js → chunk-KMZXTBLA.js} +2 -2
- package/dist/{chunk-6BQM7TOW.js → chunk-L67WTZX2.js} +3 -7
- package/dist/{chunk-CGKZDODG.js → chunk-MG67KXU7.js} +0 -5
- package/dist/{chunk-JZWELPFH.js → chunk-MRAGS5VP.js} +1 -1
- package/dist/{chunk-2UUXIIOC.js → chunk-O2X2CCKH.js} +3 -3
- package/dist/{chunk-TQWGQCNV.js → chunk-OMIQMMME.js} +3 -3
- package/dist/{chunk-UB5FZ43T.js → chunk-PCUQZDZA.js} +475 -366
- package/dist/{chunk-KVEHHC7W.js → chunk-PHHAYRQV.js} +7 -9
- package/dist/{chunk-SSSZUVWM.js → chunk-QP53RWAD.js} +88 -38
- package/dist/{chunk-DMNLFNTW.js → chunk-QT4ONTST.js} +1 -1
- package/dist/{chunk-RW5LP57K.js → chunk-SDK6KDJN.js} +0 -1
- package/dist/{chunk-MLRCSJYX.js → chunk-V7EVFLAK.js} +87 -90
- package/dist/{chunk-E4EVGPDX.js → chunk-XQXJKJ3P.js} +0 -2
- package/dist/{credit-ledger-2DFQHNLB.js → credit-ledger-5ZEJRI46.js} +1 -1
- package/dist/{credit-menu-s5HmGCqx.d.ts → credit-menu-D4Gcdgc4.d.ts} +488 -644
- package/dist/{fx-D860pZvP.d.ts → fx-B0SLBe5x.d.ts} +38 -82
- package/dist/index.d.ts +11 -14
- package/dist/index.js +2 -2
- package/dist/internal/caller.d.ts +618 -1525
- package/dist/internal/caller.js +28 -60
- package/dist/internal/server.d.ts +36 -61
- package/dist/internal/server.js +11 -11
- package/dist/{job-store-BUGqvCfL.d.ts → job-store-B2uZvga4.d.ts} +70 -59
- package/dist/{lightning-backend-BozcevPZ.d.ts → lightning-backend-CQBnQgsT.d.ts} +19 -27
- package/dist/{memory-credit-ledger-MNUOTQO5.js → memory-credit-ledger-ZOH6C3N4.js} +2 -2
- package/dist/{mpp-setup-4FJD6ZHV.js → mpp-setup-IOJBF7DB.js} +1 -1
- package/dist/{payout-reporter-RG6XNGPI.js → payout-reporter-5PIRYFVQ.js} +1 -1
- package/dist/{postgres-consumed-credential-store-VHBT4KEA.js → postgres-consumed-credential-store-ISRHBMOU.js} +1 -1
- package/dist/{postgres-job-store-3RAXMNSY.js → postgres-job-store-OGQ6IT4U.js} +1 -1
- package/dist/{postgres-kv-store-JFBDP5IP.js → postgres-kv-store-D5E2EZ24.js} +1 -1
- package/dist/{postgres-replay-store-UJXRT6VO.js → postgres-replay-store-IZFLTTAC.js} +1 -1
- package/dist/{pricing-4CEB34RM.js → pricing-MU5GNUJZ.js} +1 -1
- package/dist/{processed-payment-store-HAA4SFNK.js → processed-payment-store-FIDI3RNH.js} +1 -1
- package/dist/{revenue-reporter-ASZ7SHHH.js → revenue-reporter-NNCNRY4C.js} +1 -1
- package/dist/server/index.d.ts +53 -59
- package/dist/server/index.js +38 -37
- package/dist/{ssrf-DbFkpDv0.d.ts → ssrf-dMooihtY.d.ts} +1 -2
- package/dist/{step-cache-5dljDqrQ.d.ts → step-cache-CXg7ziML.d.ts} +389 -551
- package/dist/{tempo-charge-store-RIFTALZK.js → tempo-charge-store-76TDAF34.js} +1 -1
- package/dist/{tempo-lifecycle-DFIXQ54Q.js → tempo-lifecycle-DXM7QXJQ.js} +3 -3
- package/dist/{tempo-wallet-4QKSV65O.js → tempo-wallet-O67H5M4N.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-BNDg1715.d.ts} +14 -16
- package/dist/{wallet-CJC8lwxx.d.ts → wallet-Dwjs5n_M.d.ts} +1 -1
- package/dist/{x402-5H27DCBE.js → x402-7S2EFINY.js} +2 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -102,6 +102,18 @@ Use the Node.js release in `.nvmrc` (Node 22). Install with `npm ci`, then run t
|
|
|
102
102
|
|
|
103
103
|
The public surface is exactly `@dvmkit/sdk`, `@dvmkit/sdk/server`, and `@dvmkit/sdk/testing`.
|
|
104
104
|
|
|
105
|
+
When you pass `env` to `serve()` or `createDVMHost()`, that object is the complete host
|
|
106
|
+
environment. SDK configuration, including provenance, builder and owner identity, and
|
|
107
|
+
mint-health defaults, reads only from it. Omit `env` to use `process.env`.
|
|
108
|
+
|
|
109
|
+
## Terminal evidence
|
|
110
|
+
|
|
111
|
+
New job receipts keep schema version `1` and add optional signed `timing` with `accepted_at_ms`, `terminal_at_ms`, and cumulative `caller_wait_ms`. Acceptance and termination use Unix milliseconds; `issued_at` remains signing time in Unix seconds. Non-completed receipts also include SDK-derived `ended_by`: explicit caller cancellation and unanswered prompt/payment expiry are `caller`; handler failures, worker death, and idle expiry during processing or working are `provider`. Handlers cannot choose this attribution. Legacy receipts omit these fields and still verify. Jobs accepted before this change, including jobs still active during an upgrade, omit the additions and do not produce terminal reports because earlier caller waits are unknown; the SDK does not invent historical fault evidence.
|
|
112
|
+
|
|
113
|
+
Hosted servers with platform URL, token, DVM identity and durable database configured enqueue every terminal job, including free jobs, through the durable report outbox to `/_internal/job-terminal`. The payload contains the DVM/job identity, capability, outcome, attribution, payment-presence flag, timing and optional receipt sequence. It contains no caller identity, payment references or amounts, input, or output. The permanent deduplication claim and outbox insert commit together; startup and periodic recovery cover a crash after terminal persistence. Deliveries can repeat, so the platform deduplicates by DVM/job identity.
|
|
114
|
+
|
|
115
|
+
Deploy the platform endpoint before deploying report-emitting servers. These reports use the existing revenue transport policy: transport/5xx failures retry and abandon after 24 hours; 4xx responses, including 404, become permanently stale. The portable receipt and report contract is in `protocol-fixtures/job-terminal-evidence.json`; compatibility is tested with the pinned published `@dvmkit/sdk@0.1.5-rc.9` verifier.
|
|
116
|
+
|
|
105
117
|
## Compatibility and versioning
|
|
106
118
|
|
|
107
119
|
This is pre-1.0 alpha software and the promises match that. Breaking changes can land in any release, they are announced in the release notes for the release that carries them, and there is no long-term support release. Security fixes are made on the latest published release rather than backported.
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
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,
|
|
@@ -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
|
|
@@ -42,8 +42,8 @@ var FxRateUnavailableError = class extends Error {
|
|
|
42
42
|
display;
|
|
43
43
|
hint;
|
|
44
44
|
/**
|
|
45
|
-
* Set only when the platform explicitly refused a *known-stale* snapshot
|
|
46
|
-
*
|
|
45
|
+
* Set only when the platform explicitly refused a *known-stale* snapshot.
|
|
46
|
+
* Absent means transient/unreachable — a distinction the payment
|
|
47
47
|
* rail's amount computation branches on: refuse on `knownStale`, degrade to
|
|
48
48
|
* the last-known rate without it. See {@link FxKnownStale}.
|
|
49
49
|
*/
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
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,20 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
149
166
|
await this.insertPending(tx, "release", payload);
|
|
150
167
|
}
|
|
151
168
|
/**
|
|
152
|
-
* Join a credit drain to the transaction that made it terminal
|
|
169
|
+
* Join a credit drain to the transaction that made it terminal.
|
|
153
170
|
*
|
|
154
171
|
* Same discipline as {@link enqueueDeposit} and the same reason: no network
|
|
155
172
|
* I/O here, only the durable queue row, written through `tx` so the report
|
|
156
173
|
* and the status CAS commit or roll back together. A drain whose row was
|
|
157
174
|
* lost between the two would overstate outstanding liability forever —
|
|
158
175
|
* 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}).
|
|
162
176
|
*/
|
|
163
177
|
async enqueueCreditDrain(tx, payload) {
|
|
164
178
|
await this.insertPending(tx, "drain", payload);
|
|
165
179
|
}
|
|
166
180
|
/**
|
|
167
181
|
* Join a payout to the transaction that commits the movement it reports
|
|
168
|
-
*
|
|
182
|
+
* the batch close, the accumulator mark, the drain booking.
|
|
169
183
|
* No network I/O, only the durable queue row through `tx`, the discipline
|
|
170
184
|
* {@link enqueueCreditDrain} set and for the same reason: the money has
|
|
171
185
|
* already moved on a rail this ledger does not own, so the platform row is
|
|
@@ -187,7 +201,7 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
187
201
|
return this.enqueue("payout", payload, payload.payoutId);
|
|
188
202
|
}
|
|
189
203
|
/**
|
|
190
|
-
* Report one rail's pending-pool snapshot
|
|
204
|
+
* Report one rail's pending-pool snapshot. Best-effort and
|
|
191
205
|
* fire-and-forget, deliberately unlike {@link reportPayout}: a snapshot is
|
|
192
206
|
* replaced by the next tick, so queueing a stale one behind an outage would
|
|
193
207
|
* only deliver figures the platform already has newer ones for.
|
|
@@ -225,7 +239,7 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
225
239
|
}
|
|
226
240
|
/**
|
|
227
241
|
* Join a credit-expiry release — or the revival that reverses one — to the
|
|
228
|
-
* transaction that made it true
|
|
242
|
+
* transaction that made it true.
|
|
229
243
|
*
|
|
230
244
|
* Same discipline as {@link enqueueCreditDrain}: no network I/O, only the
|
|
231
245
|
* durable queue row written through `tx`. A release whose row was lost after
|
|
@@ -237,7 +251,7 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
237
251
|
await this.insertPending(tx, "expiry_release", payload);
|
|
238
252
|
}
|
|
239
253
|
/**
|
|
240
|
-
* Report a reaper-force-failed paid job to the platform
|
|
254
|
+
* Report a reaper-force-failed paid job to the platform so the
|
|
241
255
|
* operator receives an ambient notice that a DVM died or wedged mid-job.
|
|
242
256
|
* Best-effort and fire-and-forget: unlike {@link report} there is no local
|
|
243
257
|
* durable queue — a non-2xx or transport failure is structured-logged
|
|
@@ -277,7 +291,7 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
277
291
|
}
|
|
278
292
|
}
|
|
279
293
|
/**
|
|
280
|
-
* Report a settled draw whose revenue event was skipped
|
|
294
|
+
* Report a settled draw whose revenue event was skipped.
|
|
281
295
|
* Best-effort and fire-and-forget: the SDK's structured
|
|
282
296
|
* `revenue_skipped_no_rail` log is the durable backstop, so a failed notice
|
|
283
297
|
* must never wedge terminal handling or the orphan-draw reconciler.
|
|
@@ -317,7 +331,6 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
317
331
|
);
|
|
318
332
|
}
|
|
319
333
|
}
|
|
320
|
-
/** Report the self-relay's Base gas gauge for platform-side paging and recovery. */
|
|
321
334
|
async reportX402SelfRelayGasBalance(payload) {
|
|
322
335
|
const url = `${this.platformUrl}/_internal/x402-self-relay-gas-balance`;
|
|
323
336
|
try {
|
|
@@ -349,7 +362,6 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
349
362
|
);
|
|
350
363
|
}
|
|
351
364
|
}
|
|
352
|
-
/** Report redacted self-relay RPC health for platform-side paging and recovery. */
|
|
353
365
|
async reportX402SelfRelayRpcHealth(payload) {
|
|
354
366
|
const url = `${this.platformUrl}/_internal/x402-self-relay-rpc-health`;
|
|
355
367
|
try {
|
|
@@ -381,11 +393,9 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
381
393
|
);
|
|
382
394
|
}
|
|
383
395
|
}
|
|
384
|
-
/** Report the Tempo operator's fee-token gauge for paging and recovery. */
|
|
385
396
|
async reportTempoSettlementBalance(payload) {
|
|
386
397
|
await this.postTempoSettlementReadiness("balance", payload);
|
|
387
398
|
}
|
|
388
|
-
/** Report a redacted operator fee rejection immediately. */
|
|
389
399
|
async reportTempoSettlementFailure(payload) {
|
|
390
400
|
await this.postTempoSettlementReadiness("failure", payload);
|
|
391
401
|
}
|
|
@@ -422,14 +432,12 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
422
432
|
);
|
|
423
433
|
}
|
|
424
434
|
}
|
|
425
|
-
/** Start the background retry loop. */
|
|
426
435
|
startRetryLoop() {
|
|
427
436
|
if (this.retryTimer) return;
|
|
428
437
|
this.retryTimer = setInterval(() => {
|
|
429
438
|
void this.drainPending();
|
|
430
439
|
}, _RevenueReporter.RETRY_INTERVAL_MS);
|
|
431
440
|
}
|
|
432
|
-
/** Stop the background retry loop. */
|
|
433
441
|
stop() {
|
|
434
442
|
if (this.retryTimer) {
|
|
435
443
|
clearInterval(this.retryTimer);
|
|
@@ -466,8 +474,8 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
466
474
|
/**
|
|
467
475
|
* Deliver one bounded batch from the durable queue.
|
|
468
476
|
*
|
|
469
|
-
*
|
|
470
|
-
*
|
|
477
|
+
* Processes due, non-stale rows; failures back off up to five minutes and
|
|
478
|
+
* become terminal after 24 hours. Successful deliveries remove their rows.
|
|
471
479
|
*/
|
|
472
480
|
async drainPending() {
|
|
473
481
|
try {
|
|
@@ -517,13 +525,11 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
517
525
|
console.error("[revenue-reporter] Retry loop error:", err);
|
|
518
526
|
}
|
|
519
527
|
}
|
|
520
|
-
/** Durably queue one report of either kind, then try it once immediately. */
|
|
521
528
|
async enqueue(kind, payload, label) {
|
|
522
529
|
const id = await this.insertPending(this.db, kind, payload);
|
|
523
530
|
void this.attemptDelivery(id, payload, kind, label).catch(() => {
|
|
524
531
|
});
|
|
525
532
|
}
|
|
526
|
-
/** Insert one pending report through either the pool or an open transaction. */
|
|
527
533
|
async insertPending(q, kind, payload) {
|
|
528
534
|
const id = randomUUID();
|
|
529
535
|
await q.query(
|
|
@@ -575,7 +581,6 @@ var RevenueReporter = class _RevenueReporter {
|
|
|
575
581
|
console.warn(`[revenue-reporter] delivery failed for ${id}: HTTP ${res.status} \u2014 will retry`);
|
|
576
582
|
throw new Error(`Platform returned ${res.status}`);
|
|
577
583
|
}
|
|
578
|
-
/** Mark a pending row terminal (stale) with a classification reason. */
|
|
579
584
|
async markStale(id, reason) {
|
|
580
585
|
await this.db.query(
|
|
581
586
|
`UPDATE ${PENDING_DVM_REPORTS_TABLE} SET stale = true, stale_reason = $1 WHERE id = $2`,
|
|
@@ -162,13 +162,11 @@ var SpanWriter = class {
|
|
|
162
162
|
void this.flush().catch(noop);
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
|
-
/** Start periodic flush to the transport. */
|
|
166
165
|
startFlush() {
|
|
167
166
|
this.flushTimer = setInterval(() => {
|
|
168
167
|
void this.flush().catch(noop);
|
|
169
168
|
}, this.flushIntervalMs);
|
|
170
169
|
}
|
|
171
|
-
/** Stop periodic flush. */
|
|
172
170
|
stopFlush() {
|
|
173
171
|
if (this.flushTimer) {
|
|
174
172
|
clearInterval(this.flushTimer);
|
|
@@ -216,12 +214,10 @@ var SpanWriter = class {
|
|
|
216
214
|
this.flushing = false;
|
|
217
215
|
}
|
|
218
216
|
}
|
|
219
|
-
/** Flush remaining spans on shutdown. */
|
|
220
217
|
async shutdown() {
|
|
221
218
|
this.stopFlush();
|
|
222
219
|
await this.flush();
|
|
223
220
|
}
|
|
224
|
-
/** Number of spans dropped due to buffer overflow. */
|
|
225
221
|
getDropCount() {
|
|
226
222
|
return this.dropCount;
|
|
227
223
|
}
|
|
@@ -229,7 +225,6 @@ var SpanWriter = class {
|
|
|
229
225
|
getTruncatedSpanCount() {
|
|
230
226
|
return this.truncatedSpanCount;
|
|
231
227
|
}
|
|
232
|
-
/** Current buffer size. */
|
|
233
228
|
getBufferSize() {
|
|
234
229
|
return this.buffer.length;
|
|
235
230
|
}
|
|
@@ -28,7 +28,7 @@ var PostgresReplayStore = class {
|
|
|
28
28
|
async init() {
|
|
29
29
|
await withSdkInitLock(this.pool, () => this.createTables());
|
|
30
30
|
}
|
|
31
|
-
/** The boot DDL itself — always runs under {@link withSdkInitLock}
|
|
31
|
+
/** The boot DDL itself — always runs under {@link withSdkInitLock}. */
|
|
32
32
|
async createTables() {
|
|
33
33
|
await this.pool.query(`
|
|
34
34
|
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
StepCache,
|
|
3
3
|
loggers
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-XQXJKJ3P.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) {
|
|
@@ -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,
|
|
@@ -454,7 +454,7 @@ function buildMppMethods(opts, env) {
|
|
|
454
454
|
// Without this mppx installs `Store.memory()`, so the consumed-hash
|
|
455
455
|
// guard is per-process and a credential replayed onto a sibling machine
|
|
456
456
|
// (or after a restart) reaches a broadcast the chain then rejects
|
|
457
|
-
// instead of a cheap refusal
|
|
457
|
+
// instead of a cheap refusal.
|
|
458
458
|
...opts.tempoCharge && { store: opts.tempoCharge.store }
|
|
459
459
|
})
|
|
460
460
|
);
|
|
@@ -468,7 +468,7 @@ function buildMppMethods(opts, env) {
|
|
|
468
468
|
// A server-driven settle/close must spend fees from the same TIP-20
|
|
469
469
|
// token the channel carries. Leaving this implicit lets an account's
|
|
470
470
|
// stale fee preference win even when that token has too little left
|
|
471
|
-
// to broadcast, while the channel token is funded
|
|
471
|
+
// to broadcast, while the channel token is funded.
|
|
472
472
|
feeToken: tempoCurrency,
|
|
473
473
|
decimals: 6,
|
|
474
474
|
unitType: "request",
|
|
@@ -200,7 +200,6 @@ var PayoutReporter = class {
|
|
|
200
200
|
warn("payout_flush_failed", { error: message(err) });
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
|
-
// ── cashu ─────────────────────────────────────────────────────────────
|
|
204
203
|
/** The hook `mark-melted` runs inside its transaction: the mark and its payout commit together. */
|
|
205
204
|
meltHook() {
|
|
206
205
|
return {
|
|
@@ -262,7 +261,6 @@ var PayoutReporter = class {
|
|
|
262
261
|
void this.snapshotCashu();
|
|
263
262
|
}
|
|
264
263
|
}
|
|
265
|
-
// ── tempo ─────────────────────────────────────────────────────────────
|
|
266
264
|
/** The host attaches the session store and ledger readers once they exist. */
|
|
267
265
|
attachTempo(reader) {
|
|
268
266
|
this.tempo = reader;
|
|
@@ -307,7 +305,7 @@ var PayoutReporter = class {
|
|
|
307
305
|
};
|
|
308
306
|
}
|
|
309
307
|
/**
|
|
310
|
-
* The operator repaired a wedged cooperative close
|
|
308
|
+
* The operator repaired a wedged cooperative close. The close's
|
|
311
309
|
* payee side landed on chain when the close did; if the live hook reported
|
|
312
310
|
* it, this upgrades that row to `repair`, otherwise it is the row. The
|
|
313
311
|
* figure is the receipt's payee total for the channel.
|
|
@@ -345,7 +343,6 @@ var PayoutReporter = class {
|
|
|
345
343
|
void this.snapshotTempo();
|
|
346
344
|
}
|
|
347
345
|
}
|
|
348
|
-
// ── x402 ──────────────────────────────────────────────────────────────
|
|
349
346
|
/** The observer the batch-settlement server attaches to and routes its manager through. */
|
|
350
347
|
x402Observer() {
|
|
351
348
|
return {
|
|
@@ -370,7 +367,7 @@ var PayoutReporter = class {
|
|
|
370
367
|
* every settle closes it. The claim delta is read off storage rather than
|
|
371
368
|
* off upstream's result, which carries only a voucher count; under the
|
|
372
369
|
* fleet lock the before/after read is consistent. A channel the claim
|
|
373
|
-
* removed on its way through — a refund's claim-then-delete
|
|
370
|
+
* removed on its way through — a refund's claim-then-delete —
|
|
374
371
|
* moved whatever it still owed before it went, and that value reaches
|
|
375
372
|
* `pay_to` in this batch too, so it counts at its pre-claim figure.
|
|
376
373
|
*/
|
|
@@ -502,7 +499,6 @@ var PayoutReporter = class {
|
|
|
502
499
|
warn("payout_batch_track_failed", { scope: ctx.scope, error: message(trackErr) });
|
|
503
500
|
}
|
|
504
501
|
}
|
|
505
|
-
// ── snapshots ─────────────────────────────────────────────────────────
|
|
506
502
|
/** Every rail this reporter can read, each on its own failure boundary. */
|
|
507
503
|
async snapshot() {
|
|
508
504
|
await this.snapshotCashu();
|
|
@@ -639,7 +635,7 @@ var PayoutReporter = class {
|
|
|
639
635
|
/**
|
|
640
636
|
* Tempo: each active channel's spent-but-unsettled balance — earned, not
|
|
641
637
|
* yet paid to the recipient — and the cooperative closes wedged between
|
|
642
|
-
* chain and ledger
|
|
638
|
+
* chain and ledger, aged past the same floor the repair queue
|
|
643
639
|
* uses so an in-flight close is not reported as stuck.
|
|
644
640
|
*/
|
|
645
641
|
async snapshotTempo() {
|
|
@@ -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
|
};
|
|
@@ -106,7 +106,7 @@ var PostgresTempoChargeStore = class {
|
|
|
106
106
|
client.release();
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
|
-
/** The boot DDL itself — always runs under {@link withSdkInitLock}
|
|
109
|
+
/** The boot DDL itself — always runs under {@link withSdkInitLock}. */
|
|
110
110
|
async createTables() {
|
|
111
111
|
await this.pool.query(`
|
|
112
112
|
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
isValidEvmPrivateKey,
|
|
3
3
|
loadConfig,
|
|
4
4
|
updateConfig
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MG67KXU7.js";
|
|
6
6
|
import {
|
|
7
7
|
DvmError,
|
|
8
8
|
resolveRpcHttpTransportOptions,
|
|
@@ -101,7 +101,7 @@ async function generateTempoKey(opts = {}) {
|
|
|
101
101
|
);
|
|
102
102
|
}
|
|
103
103
|
if (existing && opts.force) {
|
|
104
|
-
const { assertTempoWalletIdle } = await import("./tempo-lifecycle-
|
|
104
|
+
const { assertTempoWalletIdle } = await import("./tempo-lifecycle-DXM7QXJQ.js");
|
|
105
105
|
await assertTempoWalletIdle("replace");
|
|
106
106
|
}
|
|
107
107
|
const { privateKeyToAccount, generatePrivateKey } = await import("viem/accounts");
|
|
@@ -155,7 +155,7 @@ async function getTempoUsdcBalance(account) {
|
|
|
155
155
|
usdc: Number(raw) / 1e6,
|
|
156
156
|
tokenAddress,
|
|
157
157
|
chainId: network.chainId,
|
|
158
|
-
//
|
|
158
|
+
// redacted here rather than at a display surface, so the clear
|
|
159
159
|
// endpoint never leaves this function on the success path either.
|
|
160
160
|
rpcUrlRedacted: redactUrl(rpcUrl),
|
|
161
161
|
rpcSource,
|