@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,16 +1,15 @@
|
|
|
1
|
-
import { ah as FundingMethod, o as PaymentMethod, bM as CreditLedgerLike, a1 as Message, X as FundingReceipt, bO as CreditSnapshot, Y as JobReceipt, S as SDKJobContext, aw as StepCache, u as ResponseContent, P as PaymentContent, a2 as MessageType, bj as X402Receipt, aE as X402Config, bi as X402ExactVersionSupport,
|
|
1
|
+
import { ah as FundingMethod, o as PaymentMethod, bM as CreditLedgerLike, a1 as Message, X as FundingReceipt, bO as CreditSnapshot, Y as JobReceipt, S as SDKJobContext, aw as StepCache, u as ResponseContent, P as PaymentContent, a2 as MessageType, bj as X402Receipt, aE as X402Config, bi as X402ExactVersionSupport, cT as X402SettlementIntent, aq as PaymentRequirementsV2, c1 as CreditLedgerQuerier, cU as X402SettlementCursor, bQ as X402SettlementStatus, cV as X402SettlementWriteOff, bN as X402RefundSettlementGate, cW as X402FacilitatorAuth, cX as X402BatchSettlementConfig, cz as PostgresX402ChannelStorage, cY as X402PayoutObserver, br as MppxServer, ab as CashuMode, cZ as CreditDepositEnqueue, c_ as X402SettlementReconciliationReason, ao as PaymentRequirements, a0 as MppxCredential, bm as CreditDepositPayload, bP as DrawResult, cn as CreditLedgerError, a6 as ReceiptCredit, ae as DrainReceiptEvent, ad as DrainReceipt, K as KVStore, y as SignedRequestAudience, c$ as CreditDrawReleaseEnqueue, cx as JobCostReportPayload, cy as JobTerminalReportPayload, cB as RevenueSkippedNoRailPayload, Z as ZodLike, a as DVMDescriptor, bV as CreditInvoiceRecord, bW as InvoiceSettlement, ch as ClientCompatibilityGate, cf as ClientCompatibility, bu as PayoutReporter, R as ResolvedCreditConfig, g as CreditView } from './step-cache-v2yhyhSZ.js';
|
|
2
2
|
import { ProofLike, SerializedDLEQ, Proof } from '@cashu/cashu-ts';
|
|
3
3
|
import { Hono, Context } from 'hono';
|
|
4
4
|
import { Pool } from 'pg';
|
|
5
|
-
import { b as FxRateSnapshot, F as FxFetcher } from './fx-
|
|
6
|
-
import { g as LockPubkey, f as CheckMintHealthOptions, b as LightningBackend, A as AttestationPayload } from './lightning-backend-
|
|
7
|
-
import { T as TopUpCapUnenforcedReason, A as AppendOutgoingOptions, J as JobRecord, b as JobStore } from './job-store-
|
|
5
|
+
import { b as FxRateSnapshot, F as FxFetcher } from './fx-DhyT1Cjh.js';
|
|
6
|
+
import { g as LockPubkey, f as CheckMintHealthOptions, b as LightningBackend, A as AttestationPayload } from './lightning-backend-BhsyrBLa.js';
|
|
7
|
+
import { T as TopUpCapUnenforcedReason, A as AppendOutgoingOptions, J as JobRecord, b as JobStore } from './job-store-D3CcZp_g.js';
|
|
8
8
|
import { Challenge } from 'mppx';
|
|
9
9
|
import { SettleResponse, SupportedResponse } from '@x402/core/types';
|
|
10
10
|
import { Channel, AutoSettlementConfig } from '@x402/evm/batch-settlement/server';
|
|
11
11
|
import { FacilitatorClient } from '@x402/core/server';
|
|
12
12
|
|
|
13
|
-
/** A row in the per-DVM `wallet_accumulator` table (internal-review). */
|
|
14
13
|
interface WalletAccumulatorRow {
|
|
15
14
|
id: string;
|
|
16
15
|
dvmId: string;
|
|
@@ -36,11 +35,8 @@ interface WalletAccumulatorRow {
|
|
|
36
35
|
* rows created before the column existed.
|
|
37
36
|
*/
|
|
38
37
|
meltQuoteId: string | null;
|
|
39
|
-
/**
|
|
40
|
-
*
|
|
41
|
-
* accepted proof has no `locktime` tag (internal-review). Drives oldest-first
|
|
42
|
-
* ordering and the future `wallet_accumulator_batch_expiring_soon` monitor.
|
|
43
|
-
*/
|
|
38
|
+
/** NUT-11 locktime at acceptance, in epoch ms; null without a tag.
|
|
39
|
+
* Drives oldest-first ordering and expiring-soon monitoring. */
|
|
44
40
|
tExpire: number | null;
|
|
45
41
|
}
|
|
46
42
|
/**
|
|
@@ -71,7 +67,7 @@ declare function initWalletAccumulatorTable(db: AccumulatorPool): Promise<void>;
|
|
|
71
67
|
*
|
|
72
68
|
* With `tx` (a client inside a caller-owned `BEGIN`), issues **no**
|
|
73
69
|
* transaction control: the inserts join the caller's transaction so the rail
|
|
74
|
-
* commit and the `CreditLedger.fund` + `draw` land atomically (
|
|
70
|
+
* commit and the `CreditLedger.fund` + `draw` land atomically (spec
|
|
75
71
|
* §2 condition 3). A `unique_violation` still surfaces as
|
|
76
72
|
* `AccumulatorReplayError`, but the caller owns the ROLLBACK — note the
|
|
77
73
|
* transaction is poisoned after the violation (Postgres aborts it), so the
|
|
@@ -82,7 +78,7 @@ declare function insertAccumulatorRows(db: AccumulatorPool, args: {
|
|
|
82
78
|
/**
|
|
83
79
|
* Per-proof matched lock pubkey, parallel to `proofs`. Recording the
|
|
84
80
|
* matched value per row lets the monitor surface retired-batch alerts
|
|
85
|
-
*
|
|
81
|
+
* and lets `dvmctl melt-pending`'s rotation walker pick the
|
|
86
82
|
* right privkey per batch. Branded `LockPubkey` so the lowercase invariant
|
|
87
83
|
* is enforced at the type boundary, not by every call site.
|
|
88
84
|
*/
|
|
@@ -97,7 +93,7 @@ declare function insertAccumulatorRows(db: AccumulatorPool, args: {
|
|
|
97
93
|
*/
|
|
98
94
|
tExpireMsByIndex?: (number | null)[];
|
|
99
95
|
receivedAt?: number;
|
|
100
|
-
/** External transaction handle — caller owns BEGIN/COMMIT/ROLLBACK
|
|
96
|
+
/** External transaction handle — caller owns BEGIN/COMMIT/ROLLBACK. */
|
|
101
97
|
tx?: AccumulatorQuerier;
|
|
102
98
|
}): Promise<WalletAccumulatorRow[]>;
|
|
103
99
|
/** Delete every accumulator row for a DVM. Used by `dvmadmin wallet-accumulator clear`. */
|
|
@@ -117,8 +113,8 @@ declare function hashLockKey(input: string): bigint;
|
|
|
117
113
|
*
|
|
118
114
|
* Used by `verifyUpfrontPayment` to reject replay of an MPP credential whose
|
|
119
115
|
* `(realm, challenge.id)` was already accepted within the current TTL window
|
|
120
|
-
*
|
|
121
|
-
* `pendingMppChallengeIds
|
|
116
|
+
* Mid-job replay is handled separately via per-job
|
|
117
|
+
* `pendingMppChallengeIds`.
|
|
122
118
|
*/
|
|
123
119
|
interface ConsumedCredentialStore {
|
|
124
120
|
/** True iff `(realm, challengeId)` was marked and the entry has not expired. */
|
|
@@ -126,7 +122,6 @@ interface ConsumedCredentialStore {
|
|
|
126
122
|
/** Mark `(realm, challengeId)` as consumed for `ttlSeconds`. */
|
|
127
123
|
mark(realm: string, challengeId: string, ttlSeconds: number): Promise<void>;
|
|
128
124
|
}
|
|
129
|
-
/** Options for `MemoryConsumedCredentialStore`. */
|
|
130
125
|
interface MemoryConsumedCredentialStoreOpts {
|
|
131
126
|
/** FIFO eviction kicks in when entry count would exceed this. Default: 10_000. */
|
|
132
127
|
maxEntries?: number;
|
|
@@ -138,8 +133,8 @@ interface MemoryConsumedCredentialStoreOpts {
|
|
|
138
133
|
*
|
|
139
134
|
* Per-process and not multi-instance-safe — each replica tracks its own
|
|
140
135
|
* consumed set, so a credential redirected to a different replica still works
|
|
141
|
-
* once.
|
|
142
|
-
*
|
|
136
|
+
* once. Use only for a single-instance DVM; swap to a shared backend before
|
|
137
|
+
* deploying multiple replicas.
|
|
143
138
|
*/
|
|
144
139
|
declare class MemoryConsumedCredentialStore implements ConsumedCredentialStore {
|
|
145
140
|
private readonly maxEntries;
|
|
@@ -175,7 +170,6 @@ interface DisposableCashuCommit {
|
|
|
175
170
|
commit(requestId: string, proofs: Proof[], account: (ledger: CreditLedgerLike) => Promise<void>): Promise<void>;
|
|
176
171
|
}
|
|
177
172
|
|
|
178
|
-
/** How often the sweep runs, and how long after boot its first pass fires. */
|
|
179
173
|
interface CreditExpirySweepOpts {
|
|
180
174
|
/** Sweep period. Defaults to {@link DEFAULT_SWEEP_INTERVAL_MS}; `0` disables the timer. */
|
|
181
175
|
intervalMs?: number;
|
|
@@ -184,26 +178,22 @@ interface CreditExpirySweepOpts {
|
|
|
184
178
|
* disables the boot pass, leaving only the interval.
|
|
185
179
|
*/
|
|
186
180
|
bootDelayMs?: number;
|
|
187
|
-
/** Credits released per pass. Passed straight to the ledger. */
|
|
188
181
|
limit?: number;
|
|
189
182
|
}
|
|
190
183
|
|
|
191
|
-
/** Promise resolver for a pending client prompt response. */
|
|
192
184
|
interface PromptResolver {
|
|
193
185
|
resolve: (value: ResponseContent) => void;
|
|
194
186
|
reject: (reason: Error) => void;
|
|
195
187
|
}
|
|
196
|
-
/** Promise resolver for a pending client payment. */
|
|
197
188
|
interface PaymentResolver {
|
|
198
189
|
resolve: (value: PaymentContent) => void;
|
|
199
190
|
reject: (reason: Error) => void;
|
|
200
191
|
}
|
|
201
|
-
/** In-memory representation of a running job. */
|
|
202
192
|
interface ServerJob {
|
|
203
193
|
id: string;
|
|
204
194
|
tags: string[];
|
|
205
195
|
/**
|
|
206
|
-
* Capability name this job belongs to
|
|
196
|
+
* Capability name this job belongs to. Set at submission from
|
|
207
197
|
* `POST /v1/job` body's `capability` field; routes the SDK runtime to the
|
|
208
198
|
* right `onJob` / `onResponse` / `onPayment` handler and the right input
|
|
209
199
|
* schema. Required — the wire layer rejects requests that omit it.
|
|
@@ -214,12 +204,12 @@ interface ServerJob {
|
|
|
214
204
|
requesterId: string;
|
|
215
205
|
/**
|
|
216
206
|
* SHA-256 hex of the per-job opaque `job_token` minted at creation for
|
|
217
|
-
* anonymous callers
|
|
207
|
+
* anonymous callers. Mirror of `JobRecord.requesterTokenHash`;
|
|
218
208
|
* see that field for the wire-level gating semantics.
|
|
219
209
|
*/
|
|
220
210
|
requesterTokenHash?: string;
|
|
221
211
|
/**
|
|
222
|
-
* Submission provenance
|
|
212
|
+
* Submission provenance — mirrors `JobRecord.requesterToken` /
|
|
223
213
|
* `requestFingerprint` / `requesterPubkey`. Carried on the job purely so
|
|
224
214
|
* `toJobRecord` persists it; nothing in the runtime reads it. It's the
|
|
225
215
|
* replay gate: a retried paid submit that reproduces the fingerprint (and,
|
|
@@ -232,6 +222,16 @@ interface ServerJob {
|
|
|
232
222
|
/** HTTP path independently recorded when the caller proof was accepted. */
|
|
233
223
|
authRequestPath?: string;
|
|
234
224
|
status: "processing" | "completed" | "failed" | "awaiting-input" | "cancelled" | "working";
|
|
225
|
+
/** Mirrors the durable completeness marker; false for legacy accepted jobs. */
|
|
226
|
+
timingKnown?: boolean;
|
|
227
|
+
/** SDK terminal attribution; never accepted from a handler. */
|
|
228
|
+
endedBy?: "caller" | "provider";
|
|
229
|
+
/** Unix ms when the terminal status committed; immutable afterwards. */
|
|
230
|
+
terminalAtMs?: number;
|
|
231
|
+
/** Accumulated awaiting-input time, excluding the currently open stretch. */
|
|
232
|
+
callerWaitMs?: number;
|
|
233
|
+
/** Unix ms at which the current awaiting-input stretch began. */
|
|
234
|
+
awaitingInputAtMs?: number;
|
|
235
235
|
summary?: string;
|
|
236
236
|
messages: Message[];
|
|
237
237
|
seq: number;
|
|
@@ -243,9 +243,9 @@ interface ServerJob {
|
|
|
243
243
|
* Settlement reference for the most-recent successful credit. Threaded
|
|
244
244
|
* into `onJobCompleted` so the platform revenue ledger can populate
|
|
245
245
|
* `revenue_events.tx_hash`. mpp/x402 carry the credential's `challenge.id`
|
|
246
|
-
* / EVM tx hash from the facilitator
|
|
247
|
-
* the `X-Cashu-Request-Id` UUID
|
|
248
|
-
* (upfront or mid-job via `processIncomingPayment`)
|
|
246
|
+
* / EVM tx hash from the facilitator; cashu accumulator carries
|
|
247
|
+
* the `X-Cashu-Request-Id` UUID. Updated on every credit
|
|
248
|
+
* (upfront or mid-job via `processIncomingPayment`).
|
|
249
249
|
* Single-row-per-job accounting keeps the most recent credit's hash,
|
|
250
250
|
* mirroring `paymentRail`.
|
|
251
251
|
*/
|
|
@@ -253,22 +253,18 @@ interface ServerJob {
|
|
|
253
253
|
/** Chain transaction returned to callers recovering x402 or Tempo acceptance. */
|
|
254
254
|
paymentTransactionHash?: string;
|
|
255
255
|
/**
|
|
256
|
-
* Rail-native
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
* so multi-credit jobs sum the per-credit native amounts (internal-review).
|
|
260
|
-
* internal-review wired the upfront credit; mid-job credits accumulate same-rail
|
|
261
|
-
* and reset on a cross-rail switch (see `processIncomingPayment`).
|
|
256
|
+
* Rail-native atomic units accumulated across same-rail credits: Tempo and
|
|
257
|
+
* x402 use stablecoin micro-units. A rail switch resets this amount; Cashu
|
|
258
|
+
* revenue is derived from paidMsats instead.
|
|
262
259
|
*/
|
|
263
260
|
nativeAmount?: number;
|
|
264
|
-
/** Native asset tag
|
|
261
|
+
/** Native asset tag paired with `nativeAmount`. */
|
|
265
262
|
nativeAsset?: "sats" | "usdc" | "usdc.e" | "usd-cents";
|
|
266
|
-
/** Cashu flow discriminator written into `revenue_events.metadata.cashu_flow` (internal-review). */
|
|
267
263
|
cashuFlow?: "p2pk_accumulator";
|
|
268
264
|
/**
|
|
269
265
|
* What this job cost the **builder** to serve, in 1e-6 of
|
|
270
266
|
* {@link ServerJob.costCurrency} — the running total the handler declared
|
|
271
|
-
* through `ctx.cost()
|
|
267
|
+
* through `ctx.cost()`. Absent until a handler declares
|
|
272
268
|
* something; absent is "unreported", which is not the same fact as a
|
|
273
269
|
* declared zero and is reported differently.
|
|
274
270
|
*
|
|
@@ -284,9 +280,9 @@ interface ServerJob {
|
|
|
284
280
|
costCurrency?: string;
|
|
285
281
|
/** Number of `ctx.cost()` declarations incorporated into the current cost state. */
|
|
286
282
|
costRevision?: number;
|
|
287
|
-
/** Credit the upfront payment funded/drew
|
|
283
|
+
/** Credit the upfront payment funded/drew. See `JobRecord.creditId`. */
|
|
288
284
|
creditId?: string;
|
|
289
|
-
/** The draw placed for this job on `creditId
|
|
285
|
+
/** The draw placed for this job on `creditId`. */
|
|
290
286
|
drawId?: string;
|
|
291
287
|
/** Original funding artifact returned by an explicit fund-and-draw. */
|
|
292
288
|
fundingReceipt?: FundingReceipt;
|
|
@@ -299,16 +295,16 @@ interface ServerJob {
|
|
|
299
295
|
* binds challenges to realm/method/amount/expiry, not to the dvmkit job-id;
|
|
300
296
|
* verifying the incoming credential's `challenge.id` is in this set blocks
|
|
301
297
|
* a credential lifted from a sibling job that requested the same fields.
|
|
302
|
-
* Cleared on successful credit
|
|
298
|
+
* Cleared on successful credit.
|
|
303
299
|
*/
|
|
304
300
|
pendingMppChallengeIds?: string[];
|
|
305
301
|
/**
|
|
306
|
-
* Per-job binding for x402 mid-job credentials
|
|
302
|
+
* Per-job binding for x402 mid-job credentials. 32-byte 0x-hex
|
|
307
303
|
* nonce issued by `requestPayment` and stamped onto the outbound x402
|
|
308
304
|
* envelope; the caller signs `TransferWithAuthorization` against this exact
|
|
309
305
|
* `bytes32`. Verification rejects an incoming `x402_payment` whose decoded
|
|
310
|
-
* authorization nonce differs
|
|
311
|
-
*
|
|
306
|
+
* authorization nonce differs before crediting the job. Cleared on
|
|
307
|
+
* successful credit.
|
|
312
308
|
*/
|
|
313
309
|
pendingX402Nonce?: string;
|
|
314
310
|
/**
|
|
@@ -318,30 +314,30 @@ interface ServerJob {
|
|
|
318
314
|
pendingX402AmountUsdcMicro?: string;
|
|
319
315
|
/**
|
|
320
316
|
* Fiat micro-units the outstanding `requestPayment` ask is worth, pinned when
|
|
321
|
-
* the payment-request was emitted
|
|
317
|
+
* the payment-request was emitted. See `JobRecord`.
|
|
322
318
|
*/
|
|
323
319
|
pendingPaymentFiatMicro?: number;
|
|
324
320
|
/** Currency of {@link pendingPaymentFiatMicro} (lowercase ISO-4217). */
|
|
325
321
|
pendingPaymentFiatCurrency?: string;
|
|
326
322
|
/**
|
|
327
323
|
* Cumulative fiat micro this job has asked for mid-job, the ceiling on its
|
|
328
|
-
* draw growth
|
|
324
|
+
* draw growth. See `JobRecord.askedTopUpMicro` for the sentinel.
|
|
329
325
|
*/
|
|
330
326
|
askedTopUpMicro?: number;
|
|
331
327
|
/**
|
|
332
328
|
* Currency of {@link askedTopUpMicro} — and this job's sticky ask
|
|
333
|
-
* denomination
|
|
329
|
+
* denomination. See `JobRecord.askedTopUpCurrency`.
|
|
334
330
|
*/
|
|
335
331
|
askedTopUpCurrency?: string;
|
|
336
332
|
/**
|
|
337
|
-
* Why this job's draw growth is not capped, when it isn't
|
|
333
|
+
* Why this job's draw growth is not capped, when it isn't. Mirror
|
|
338
334
|
* of `JobRecord.topUpCapUnenforcedReason`.
|
|
339
335
|
*/
|
|
340
336
|
topUpCapUnenforcedReason?: TopUpCapUnenforcedReason;
|
|
341
337
|
/** Price the job was charged at (msats). Used to detect overpayment for change/melt gating. */
|
|
342
338
|
requiredMsats?: number;
|
|
343
339
|
/**
|
|
344
|
-
* The signed receipt for this job's terminal outcome
|
|
340
|
+
* The signed receipt for this job's terminal outcome. Mirror of
|
|
345
341
|
* `JobRecord.receipt`, set once the terminal funnel has signed and persisted
|
|
346
342
|
* it so the local read paths (which serve from `activeJobs` during the
|
|
347
343
|
* cleanup window) hand back the same bytes as a cross-machine re-read.
|
|
@@ -349,7 +345,7 @@ interface ServerJob {
|
|
|
349
345
|
receipt?: JobReceipt;
|
|
350
346
|
/**
|
|
351
347
|
* The in-flight terminal chain — persist, then sign + store the receipt
|
|
352
|
-
*
|
|
348
|
+
* Assigned synchronously by `ctx.complete()` / `ctx.fail()` at
|
|
353
349
|
* the instant the job goes terminal, so the `POST /v1/job` fast path can
|
|
354
350
|
* await it (bounded) and put the receipt on the synchronous 200 rather than
|
|
355
351
|
* making the caller poll for it. Never rejects — the chain swallows its own
|
|
@@ -358,7 +354,7 @@ interface ServerJob {
|
|
|
358
354
|
terminalWork?: Promise<void>;
|
|
359
355
|
listeners: Set<(msg: Message) => void>;
|
|
360
356
|
/**
|
|
361
|
-
* Cancellation signal for the running handler
|
|
357
|
+
* Cancellation signal for the running handler. Aborted whenever
|
|
362
358
|
* the job is cancelled on this process — caller cancel, idle timeout,
|
|
363
359
|
* stale-sweep teardown, supersession. Surfaced to builders as `ctx.signal`
|
|
364
360
|
* and auto-threaded into `ctx.fetch`, so an in-flight provider call
|
|
@@ -368,18 +364,18 @@ interface ServerJob {
|
|
|
368
364
|
*/
|
|
369
365
|
abort: AbortController;
|
|
370
366
|
/**
|
|
371
|
-
* Hook for cross-machine message persistence (Tx A
|
|
367
|
+
* Hook for cross-machine message persistence (Tx A). Set by
|
|
372
368
|
* `JobManager` when the store is streamable. The appender runs the
|
|
373
369
|
* outgoing-message tx atomically with any payment-request counter bump so
|
|
374
370
|
* `(message, pending_payment_msats)` land together — never separately.
|
|
375
371
|
*
|
|
376
|
-
* Per-job serialised
|
|
372
|
+
* Per-job serialised: each call chains onto `messageAppenderTail`
|
|
377
373
|
* so two synchronous `providerMessage` calls (e.g. `artifact` followed by
|
|
378
374
|
* `complete`) can't race for the DB-side seq allocation.
|
|
379
375
|
*/
|
|
380
376
|
messageAppender?: (msg: Message, opts?: AppendOutgoingOptions) => void;
|
|
381
377
|
/**
|
|
382
|
-
* Tail of the per-job appender chain
|
|
378
|
+
* Tail of the per-job appender chain. `wireMessageAppender` updates
|
|
383
379
|
* this on every call so `persistJob` can await it before the terminal snapshot
|
|
384
380
|
* save runs `DELETE FROM job_messages`.
|
|
385
381
|
*/
|
|
@@ -406,12 +402,11 @@ interface ServerJob {
|
|
|
406
402
|
/** Unix ms timestamp of last activity (message, yield, terminal). */
|
|
407
403
|
lastActivityAt: number;
|
|
408
404
|
}
|
|
409
|
-
/** Extract the persistent fields from a ServerJob into a JobRecord. */
|
|
410
405
|
declare function toJobRecord(job: ServerJob): JobRecord;
|
|
411
|
-
/**
|
|
406
|
+
/** Restored snapshots receive fresh process-local listeners, cancellation and prompt state. */
|
|
412
407
|
declare function fromJobRecord(record: JobRecord): ServerJob;
|
|
413
408
|
/**
|
|
414
|
-
* Signal cancellation to a running handler
|
|
409
|
+
* Signal cancellation to a running handler. Idempotent — aborting an
|
|
415
410
|
* already-aborted controller is a no-op, so the overlapping teardown paths
|
|
416
411
|
* (caller cancel then stale sweep, say) can each call it unconditionally.
|
|
417
412
|
*/
|
|
@@ -424,41 +419,14 @@ declare function abortJob(job: ServerJob, reason: string): void;
|
|
|
424
419
|
declare class JobCancelledError extends Error {
|
|
425
420
|
constructor(message?: string);
|
|
426
421
|
}
|
|
427
|
-
/** Append a provider-sent message to the job and notify SSE listeners. */
|
|
428
422
|
declare function providerMessage(job: ServerJob, type: MessageType, content: Record<string, unknown> | object): void;
|
|
429
|
-
/** Check if a job status is terminal (completed, failed, cancelled). */
|
|
430
423
|
declare function isTerminal(status: ServerJob["status"]): boolean;
|
|
431
|
-
/** Check if a message is a yield point (ends the provider's turn). */
|
|
432
424
|
declare function isYieldMessage(msg: Message): boolean;
|
|
433
425
|
|
|
434
|
-
/**
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
* mint on a timer, tracks consecutive failures, and flips a mint to `sick`
|
|
438
|
-
* once the threshold is crossed. The SDK uses the resulting view to:
|
|
439
|
-
*
|
|
440
|
-
* 1. Filter `/v1/info`'s advertised `mints` to currently-healthy ones.
|
|
441
|
-
* 2. Reject new accumulator receives at sick mints before they touch the
|
|
442
|
-
* DB (`cashu_mint_sick` 503 from `verifyAccumulatorReceipt`).
|
|
443
|
-
*
|
|
444
|
-
* Since internal-review this tracker is the sole owner of mint health: boot no longer
|
|
445
|
-
* runs a fail-fast check before binding the port. To preserve what boot used
|
|
446
|
-
* to guarantee it (a) treats a mint as healthy only when it's reachable AND
|
|
447
|
-
* NUT-compliant (reproducing `assertNutSupport` via `nutCompliant`), and (b)
|
|
448
|
-
* exposes `initialProbeComplete()` so the paid path holds calls at
|
|
449
|
-
* `mint_health_pending` until the first probe validates ≥1 mint.
|
|
450
|
-
*
|
|
451
|
-
* Existing accumulator entries are not touched — they're P2PK-locked to the
|
|
452
|
-
* original mint and stay put until `dvmctl melt-pending` drains them. The
|
|
453
|
-
* accumulator monitor's `wallet_accumulator_batch_expiring_soon` already
|
|
454
|
-
* pages the builder when a stuck batch nears `t_expire`.
|
|
455
|
-
*
|
|
456
|
-
* In-memory only — no DB persistence. The platform cron keeps the durable
|
|
457
|
-
* record + email path (internal-review); the SDK only needs fresh in-process state
|
|
458
|
-
* to make routing decisions.
|
|
459
|
-
*/
|
|
426
|
+
/** In-process mint health gates advertising and new receives. Paid calls wait for
|
|
427
|
+
* the first probe; NUT incompatibility fails immediately, transient failures
|
|
428
|
+
* use a consecutive threshold. Existing accumulator proofs remain at their mint. */
|
|
460
429
|
|
|
461
|
-
/** Per-mint runtime view exposed by `snapshot()`. */
|
|
462
430
|
interface MintHealthSnapshot {
|
|
463
431
|
/** Last-observed wall-clock latency of the probe in ms. */
|
|
464
432
|
latencyMs?: number;
|
|
@@ -467,8 +435,8 @@ interface MintHealthSnapshot {
|
|
|
467
435
|
/**
|
|
468
436
|
* True once the mint is considered unhealthy for routing: either
|
|
469
437
|
* `consecutiveFailures` crossed the threshold, or the last probe reached the
|
|
470
|
-
* mint but it fails the NUT gate
|
|
471
|
-
* that flips `sick` immediately
|
|
438
|
+
* mint but it fails the NUT gate, a deterministic, permanent config error
|
|
439
|
+
* that flips `sick` immediately.
|
|
472
440
|
*/
|
|
473
441
|
sick: boolean;
|
|
474
442
|
/**
|
|
@@ -478,15 +446,14 @@ interface MintHealthSnapshot {
|
|
|
478
446
|
* never resolves without a config/mint change.
|
|
479
447
|
*/
|
|
480
448
|
nutIncompatible?: boolean;
|
|
481
|
-
/** Mint version reported by the last successful probe. */
|
|
482
449
|
version?: string;
|
|
483
|
-
/** Error category from the most recent failed probe. */
|
|
484
450
|
errorCode?: string;
|
|
485
451
|
}
|
|
486
|
-
/** Tunables for {@link MintHealthTracker}. */
|
|
487
452
|
interface MintHealthTrackerOptions {
|
|
488
453
|
/** Cashu mint URLs the DVM accepts. The tracker probes every entry. */
|
|
489
454
|
mints: string[];
|
|
455
|
+
/** Host environment used for default timing and failure settings. */
|
|
456
|
+
env?: Record<string, string | undefined>;
|
|
490
457
|
/**
|
|
491
458
|
* Tick cadence in ms. Defaults to {@link DEFAULT_TICK_INTERVAL_MS} or the
|
|
492
459
|
* `DVMKIT_MINT_HEALTH_TICK_MS` env var when set.
|
|
@@ -497,26 +464,11 @@ interface MintHealthTrackerOptions {
|
|
|
497
464
|
* {@link DEFAULT_FAILURES_TO_SICK} or `DVMKIT_MINT_FAILURES_TO_SICK` env.
|
|
498
465
|
*/
|
|
499
466
|
consecutiveFailuresToSick?: number;
|
|
500
|
-
/** Injected `fetch` for tests. */
|
|
501
467
|
fetchImpl?: typeof fetch;
|
|
502
|
-
/** Injected `Date.now` for tests. */
|
|
503
468
|
now?: () => number;
|
|
504
|
-
/**
|
|
505
|
-
* Per-probe timeout / retry / backoff, forwarded to {@link checkMintHealth}.
|
|
506
|
-
* Defaults to that function's own defaults — 3 attempts with `[200, 500]`
|
|
507
|
-
* backoff, which is what a real tick should do.
|
|
508
|
-
*
|
|
509
|
-
* Tests that drive several failing ticks set `backoffMs: [0]`: a failing
|
|
510
|
-
* probe otherwise costs 700ms of real `setTimeout`, and four of them put a
|
|
511
|
-
* unit test within a loaded CI runner's reach of the 5s default timeout.
|
|
512
|
-
* `fetchImpl` is set on the tracker, not here.
|
|
513
|
-
*/
|
|
469
|
+
/** Forwarded to checkMintHealth; fetchImpl is supplied on the tracker. */
|
|
514
470
|
probe?: Omit<CheckMintHealthOptions, "fetchImpl">;
|
|
515
471
|
}
|
|
516
|
-
/**
|
|
517
|
-
* Runtime mint-health tracker. Owns its own timer; `start()` runs an initial
|
|
518
|
-
* probe immediately so the first received call doesn't see a stale snapshot.
|
|
519
|
-
*/
|
|
520
472
|
declare class MintHealthTracker {
|
|
521
473
|
private readonly mints;
|
|
522
474
|
private readonly tickIntervalMs;
|
|
@@ -542,7 +494,7 @@ declare class MintHealthTracker {
|
|
|
542
494
|
/**
|
|
543
495
|
* True once the first full probe tick has landed. Before this, paid calls
|
|
544
496
|
* are held at `503 mint_health_pending` rather than accepted against
|
|
545
|
-
* unvalidated mints
|
|
497
|
+
* unvalidated mints.
|
|
546
498
|
*/
|
|
547
499
|
initialProbeComplete(): boolean;
|
|
548
500
|
/**
|
|
@@ -559,63 +511,34 @@ declare class MintHealthTracker {
|
|
|
559
511
|
* gate — a deterministic, permanent config error, unlike a transient outage.
|
|
560
512
|
* Unknown mints return false. `advertisedMints()` uses this to keep a
|
|
561
513
|
* permanently-unsettleable mint off `/v1/info` even in the all-sick fallback,
|
|
562
|
-
* where a merely-flapping mint is still published
|
|
514
|
+
* where a merely-flapping mint is still published.
|
|
563
515
|
*/
|
|
564
516
|
isNutIncompatible(mintUrl: string): boolean;
|
|
565
|
-
/** Plain-object snapshot keyed by mint URL — for tests + observability. */
|
|
566
517
|
snapshot(): Record<string, MintHealthSnapshot>;
|
|
567
518
|
private applyResult;
|
|
568
|
-
/**
|
|
569
|
-
* One-shot structured summary emitted after the first probe tick. Replaces
|
|
570
|
-
* the boot check's per-mint `cashu_mint_startup_health` lines and surfaces an
|
|
571
|
-
* all-mints-unhealthy warning — the operator alert that the Fly restart-loop
|
|
572
|
-
* used to be before boot stopped refusing to start (internal-review).
|
|
573
|
-
*/
|
|
519
|
+
/** One boot summary, including an all-unhealthy warning. */
|
|
574
520
|
private logBootHealth;
|
|
575
521
|
}
|
|
576
522
|
|
|
577
523
|
/**
|
|
578
|
-
* Credit fields
|
|
579
|
-
*
|
|
580
|
-
* An explicit draw references the credit INSIDE the secp256k1/Schnorr-signed
|
|
581
|
-
* `body.data` — no new caller crypto. Wire field names (snake_case, matching
|
|
582
|
-
* the rest of the wire surface):
|
|
583
|
-
*
|
|
584
|
-
* - `credit_id` — the credit to draw against.
|
|
585
|
-
* - `draw_id` — client-generated draw id; the ledger's idempotency key.
|
|
586
|
-
* - `fund` — optional `{ amount_micro, commitment }` funding commitment:
|
|
587
|
-
* present iff a funding artifact (X-Cashu / X-PAYMENT / mpp credential)
|
|
588
|
-
* rides the same request. `commitment` is the SHA-256 hex over the raw
|
|
589
|
-
* artifact bytes, binding the header-borne proof — which sits OUTSIDE the
|
|
590
|
-
* signed body — to the credit its sender intended (spec §2 condition 3).
|
|
524
|
+
* Credit fields belong to the raw signed body. The funding commitment hashes the header
|
|
525
|
+
* artifact, binding money outside that body to its intended credit.
|
|
591
526
|
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
* creditEnvelopeIgnoreFields} feeding the auth verifier's own schema check), so
|
|
596
|
-
* handlers never see them and a top-level `.strict()` capability schema is
|
|
597
|
-
* fine (internal-review). Relying on Zod's default strip mode instead was the trap:
|
|
598
|
-
* `.strict()` rejects unknown keys, so an explicit draw 400'd at parse before
|
|
599
|
-
* the envelope was ever extracted.
|
|
600
|
-
*
|
|
601
|
-
* The strip never touches the canonical signing bytes. Every auth call site
|
|
602
|
-
* verifies the raw wire body (`signedRequestInput`, internal-review), so the credit
|
|
603
|
-
* fields are in the verified bytes because they were never taken out of them —
|
|
604
|
-
* the strip is only ever applied to what a schema is about to parse.
|
|
527
|
+
* Schema projection strips undeclared protocol keys before builder input parsing, without
|
|
528
|
+
* changing canonical signing bytes. Signature verification must use the original wire body; a
|
|
529
|
+
* parsed projection may omit credit fields.
|
|
605
530
|
*/
|
|
606
531
|
interface CreditEnvelope {
|
|
607
532
|
creditId: string;
|
|
608
533
|
drawId: string;
|
|
609
534
|
fund?: CreditFundCommitment;
|
|
610
535
|
}
|
|
611
|
-
/** The signed funding commitment for a fund-and-draw request. */
|
|
612
536
|
interface CreditFundCommitment {
|
|
613
537
|
/** Funding amount in 1e-6 units of the credit's currency. */
|
|
614
538
|
amountMicro: number;
|
|
615
539
|
/** SHA-256 hex over the raw funding artifact bytes. */
|
|
616
540
|
commitment: string;
|
|
617
541
|
}
|
|
618
|
-
/** Wire keys reserved for the credit envelope inside `body.data`. */
|
|
619
542
|
declare const CREDIT_ENVELOPE_KEYS: readonly ["credit_id", "draw_id", "fund"];
|
|
620
543
|
/**
|
|
621
544
|
* Thrown by {@link extractCreditEnvelope} on a structurally invalid credit
|
|
@@ -633,36 +556,24 @@ declare class CreditEnvelopeError extends Error {
|
|
|
633
556
|
*/
|
|
634
557
|
declare function extractCreditEnvelope(data: unknown): CreditEnvelope | undefined;
|
|
635
558
|
/**
|
|
636
|
-
*
|
|
637
|
-
*
|
|
638
|
-
*
|
|
639
|
-
*
|
|
640
|
-
* one exception this exists for is `/v1/credit`'s own `CREDIT_REQUEST_SCHEMA`,
|
|
641
|
-
* where `credit_id` and `fund` are real fields on a different wire shape and
|
|
642
|
-
* removing them would empty the request. It is not an escape hatch for a
|
|
643
|
-
* capability: the three names stay reserved on `body.data`, and declaring one
|
|
644
|
-
* doesn't win a builder their own value — `app.ts` runs
|
|
645
|
-
* {@link extractCreditEnvelope} over the raw body on every submit, so a
|
|
646
|
-
* capability that declares `fund` answers `invalid_credit_fields` instead.
|
|
559
|
+
* Return protocol keys not declared by the schema. Schema-declared keys remain available,
|
|
560
|
+
* including /v1/credit’s differently shaped credit_id and fund fields. Submit separately
|
|
561
|
+
* extracts and validates the credit envelope from raw body.data; declaring a key does not
|
|
562
|
+
* bypass that validation.
|
|
647
563
|
*/
|
|
648
564
|
declare function creditEnvelopeIgnoreFields(schema: unknown): readonly string[];
|
|
649
565
|
/**
|
|
650
|
-
* Remove
|
|
651
|
-
*
|
|
652
|
-
*
|
|
653
|
-
*
|
|
654
|
-
* Returns `data` unchanged when it isn't a plain object or carries none of the
|
|
655
|
-
* removable keys, so the ordinary no-credit request allocates nothing. Never
|
|
656
|
-
* use the result to derive canonical signing bytes: those cover the wire form,
|
|
657
|
-
* credit fields included (`signedRequestInput`).
|
|
566
|
+
* Remove undeclared credit keys before schema parsing. Preserve nonobject/array inputs and
|
|
567
|
+
* objects with no removable keys. Never derive signing bytes from this projection; signatures
|
|
568
|
+
* cover the raw wire body.
|
|
658
569
|
*/
|
|
659
570
|
declare function stripCreditEnvelope(schema: unknown, data: unknown): unknown;
|
|
660
571
|
|
|
661
572
|
/**
|
|
662
|
-
* Fiat denomination of the job price, pinned per request
|
|
573
|
+
* Fiat denomination of the job price, pinned per request.
|
|
663
574
|
*
|
|
664
|
-
* The credit ledger is fiat-micro denominated
|
|
665
|
-
*
|
|
575
|
+
* The credit ledger is fiat-micro denominated: 1e-6 of the DVM's pricing
|
|
576
|
+
* currency, never msats. The conversion pins at
|
|
666
577
|
* quote time: the verify path already fixes both the fiat price and its msat
|
|
667
578
|
* equivalent, so the implicit N=1 fund credits the QUOTED fiat amount when
|
|
668
579
|
* the attached token satisfies the quoted msats — no fresh BTC/USD lookup in
|
|
@@ -677,36 +588,13 @@ interface PriceFiat {
|
|
|
677
588
|
}
|
|
678
589
|
|
|
679
590
|
/**
|
|
680
|
-
*
|
|
681
|
-
*
|
|
682
|
-
*
|
|
683
|
-
*
|
|
684
|
-
* price form that needs one, and the ledger can be denominated for every paid
|
|
685
|
-
* request rather than only the ones a rate provider happened to be up for.
|
|
686
|
-
*
|
|
687
|
-
* - `dynamicUpfront` — the validated `QuoteResult.upfront` fiat envelope a
|
|
688
|
-
* dynamic-priced capability returned for THIS request.
|
|
689
|
-
* - a static `"$X.XX"` price — parsed directly.
|
|
690
|
-
*
|
|
691
|
-
* Returns `undefined` for free jobs (`priceMsats` unset or 0). For a paid job
|
|
692
|
-
* it always returns a denomination: `validateQuoteResult` rejects a dynamic
|
|
693
|
-
* amount too small or too large to express in micro-units, and `configureDVM`
|
|
694
|
-
* rejects a static price with the same defect at boot.
|
|
695
|
-
*
|
|
696
|
-
* internal-review: whichever branch answers, the currency is the DVM's declared
|
|
697
|
-
* pricing currency, so a job draws in the denomination `/v1/credit` funded in.
|
|
698
|
-
* Held there by two guards outside this function, which is why neither branch
|
|
699
|
-
* needs to know the DVM's currency to return it — `validateQuoteResult`
|
|
700
|
-
* refuses a quote that disagrees with the declaration, and `configureDVM`
|
|
701
|
-
* refuses a static (USD-literal) price on a DVM that declares anything but
|
|
702
|
-
* `"usd"`. Both refuse rather than convert, deliberately: an fx lookup on this
|
|
703
|
-
* path is the internal-review shape, where a cold-start rate outage took money and
|
|
704
|
-
* recorded no draw.
|
|
591
|
+
* Paid requests pin the validated fiat price without an FX lookup. Free jobs
|
|
592
|
+
* return undefined. configureDVM and validateQuoteResult enforce micro-unit
|
|
593
|
+
* range and the DVM's declared currency before this helper is called; neither
|
|
594
|
+
* guard converts a mismatched currency.
|
|
705
595
|
*/
|
|
706
596
|
declare function resolvePriceFiat(args: {
|
|
707
|
-
/** The capability's static `price` value, if any. */
|
|
708
597
|
price: string | undefined;
|
|
709
|
-
/** The resolved required price in msats (static or dynamic). */
|
|
710
598
|
priceMsats: number | undefined;
|
|
711
599
|
/** Fiat envelope from a dynamic quote, when the price came from `onQuote`. */
|
|
712
600
|
dynamicUpfront?: {
|
|
@@ -715,28 +603,11 @@ declare function resolvePriceFiat(args: {
|
|
|
715
603
|
};
|
|
716
604
|
}): PriceFiat | undefined;
|
|
717
605
|
/**
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
* A caller doing an explicit fund-and-draw has to sign the provider's own
|
|
723
|
-
* valuation of the artifact it attaches (`price_micro × paid_msats /
|
|
724
|
-
* required_msats`), and `price_micro` is `PriceFiat.amountMicro`. Reconstructing
|
|
725
|
-
* that integer from a rendered decimal is lossy the moment any surface between
|
|
726
|
-
* the two rounds — the isolate tier advertises at four decimals, and the CLI's
|
|
727
|
-
* synthetic quote used to re-round a 402 the same way — so `/v1/quote`'s
|
|
728
|
-
* `upfront.amount_micro` and the upfront 402's `price_micro` state the integer
|
|
729
|
-
* itself. Both **must** derive through here rather than recomputing: two
|
|
730
|
-
* independent derivations of one figure is the shape that produced the mismatch
|
|
731
|
-
* these fields exist to remove.
|
|
732
|
-
*
|
|
733
|
-
* The `priceMsats` gate stays on `resolvePriceFiat` alone. An advertisement
|
|
734
|
-
* surface has no msat figure in hand — the quote route states a price before
|
|
735
|
-
* anything has converted it — and a free capability is already excluded there
|
|
736
|
-
* by having no price to pass.
|
|
606
|
+
* Shared exact integer for quote, 402 and ledger valuation. Callers must sign
|
|
607
|
+
* this micro-unit figure rather than reconstruct it from a rounded display.
|
|
608
|
+
* Unlike resolvePriceFiat, advertisement callers need no msat price in hand.
|
|
737
609
|
*/
|
|
738
610
|
declare function priceFiatMicro(args: {
|
|
739
|
-
/** The capability's static `price` value, if any. */
|
|
740
611
|
price?: string | undefined;
|
|
741
612
|
/** Fiat envelope from a dynamic quote, when the price came from `onQuote`. */
|
|
742
613
|
dynamicUpfront?: {
|
|
@@ -744,17 +615,7 @@ declare function priceFiatMicro(args: {
|
|
|
744
615
|
currency: string;
|
|
745
616
|
};
|
|
746
617
|
}): PriceFiat | undefined;
|
|
747
|
-
/**
|
|
748
|
-
* Why a denomination could not be produced (internal-review). Carried out of
|
|
749
|
-
* {@link msatsToFiatMicro} and {@link resolveFxSnapshot} so the skip that
|
|
750
|
-
* follows can name its cause.
|
|
751
|
-
*
|
|
752
|
-
* Every one of these used to collapse into a bare `undefined`, which is how a
|
|
753
|
-
* `credit_topup_skipped` / `denomination_unavailable` line came to be
|
|
754
|
-
* undiagnosable: a rate outage, a currency the snapshot never carried, and an
|
|
755
|
-
* ask too small to express in micro-units all read identically in the log, and
|
|
756
|
-
* the only way to tell them apart was to re-derive the whole path by hand.
|
|
757
|
-
*/
|
|
618
|
+
/** Reasons are kept distinct so a skipped ledger leg identifies its cause. */
|
|
758
619
|
type FiatDenominationFailure =
|
|
759
620
|
/** Nothing to convert — a zero-msat ask. */
|
|
760
621
|
"no_msats"
|
|
@@ -771,7 +632,7 @@ type FiatDenominationFailure =
|
|
|
771
632
|
| "rate_stale_refused";
|
|
772
633
|
/**
|
|
773
634
|
* An fx snapshot plus how fresh it is, or why there is none. `stale` means the
|
|
774
|
-
* live fetch failed and this is the last snapshot the fetcher saw
|
|
635
|
+
* live fetch failed and this is the last snapshot the fetcher saw;
|
|
775
636
|
* `cachedAt` is the Unix ms it landed.
|
|
776
637
|
*/
|
|
777
638
|
type ResolvedFx = {
|
|
@@ -790,7 +651,7 @@ type ResolvedFx = {
|
|
|
790
651
|
* `stale` covers both degradation rungs and `carriedForward` distinguishes
|
|
791
652
|
* them: either the whole snapshot came from `lastKnown()` because the live
|
|
792
653
|
* fetch failed, or the fetch succeeded and only *this* currency was carried
|
|
793
|
-
* over from an earlier one because the body omitted it
|
|
654
|
+
* over from an earlier one because the body omitted it. They read
|
|
794
655
|
* differently to an operator — the rate source down, versus the rate source
|
|
795
656
|
* having quietly narrowed — so the log says which.
|
|
796
657
|
*/
|
|
@@ -806,80 +667,51 @@ type FiatDenomination = {
|
|
|
806
667
|
detail?: string;
|
|
807
668
|
};
|
|
808
669
|
/**
|
|
809
|
-
*
|
|
810
|
-
*
|
|
811
|
-
*
|
|
812
|
-
* The same ladder `resolveServerBtcUsdRate` walks for the x402/Tempo rails
|
|
813
|
-
* (`btc-rate.ts`), and it exists here because the mid-job top-up's two fx legs
|
|
814
|
-
* — the ask-time pin in `context.ts` and the verify-time hop in
|
|
815
|
-
* {@link msatsToFiatMicro} — were the only ones in the payment layer that
|
|
816
|
-
* called `fetch()` raw and swallowed the throw. `FxFetcher.lastKnown` is
|
|
817
|
-
* documented as "the offline degradation source for the payment rail", so a
|
|
818
|
-
* process holding a perfectly good snapshot was nonetheless dropping the
|
|
819
|
-
* receipt's whole `credit` block over one blip at the rate provider.
|
|
820
|
-
*
|
|
821
|
-
* **A known-stale refusal fails closed and never degrades** (internal-review): the
|
|
822
|
-
* platform decided the snapshot is too old to price against, so serving the
|
|
823
|
-
* last-known one prices off exactly what it refused. Every other failure
|
|
824
|
-
* degrades, which is the internal-review posture.
|
|
825
|
-
*
|
|
826
|
-
* No cool-down latch, unlike `resolveServerBtcUsdRate`. That one guards a
|
|
827
|
-
* per-request hot path; this runs at most twice per mid-job ask, and a latch
|
|
828
|
-
* shared across every DVM in the process would suppress the recovery probe a
|
|
829
|
-
* long-lived host needs.
|
|
670
|
+
* A live fetch failure may use lastKnown(), but a known-stale refusal never
|
|
671
|
+
* does: that would use the very snapshot the platform refused. No shared
|
|
672
|
+
* cooldown suppresses the recovery probe on a later mid-job ask.
|
|
830
673
|
*/
|
|
831
674
|
declare function resolveFxSnapshot(fetcher: FxFetcher): Promise<ResolvedFx>;
|
|
832
675
|
/**
|
|
833
|
-
*
|
|
834
|
-
*
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
* internal-review removed the numeric-msats *price*, so {@link resolvePriceFiat} needs
|
|
838
|
-
* no rate; the mid-job ask is the one surface where a bare msat amount survives
|
|
839
|
-
* (`ctx.requestPayment(50_000, …)`), and the credit it tops up may be
|
|
840
|
-
* denominated in a currency the ask never named. An fx hop is the only way to
|
|
841
|
-
* put either on the ledger.
|
|
842
|
-
*
|
|
843
|
-
* Answers a *reason* rather than `undefined` when it can't denominate — the
|
|
844
|
-
* caller still accepts the money and skips the ledger leg rather than refusing
|
|
845
|
-
* over a rate outage (internal-review), but it can now say which failure it degraded
|
|
846
|
-
* on (internal-review). That fail-soft is why this stays separate from
|
|
847
|
-
* `resolvePriceFiat`, which is now total for every paid request.
|
|
676
|
+
* Bare-msat mid-job asks need an FX conversion into the credit's currency.
|
|
677
|
+
* Failure carries a reason: callers accept the payment and skip its ledger
|
|
678
|
+
* leg rather than refusing over an outage. Upfront prices use resolvePriceFiat
|
|
679
|
+
* and require no rate, so they do not share this fail-soft behavior.
|
|
848
680
|
*/
|
|
849
681
|
declare function msatsToFiatMicro(args: {
|
|
850
682
|
msats: number;
|
|
851
683
|
currency: string;
|
|
852
684
|
fxFetcher: FxFetcher;
|
|
853
685
|
/**
|
|
854
|
-
* A snapshot already in hand
|
|
686
|
+
* A snapshot already in hand. The mid-job ask path fetches one to
|
|
855
687
|
* size a fiat-form ask's msats; converting on that same snapshot costs no
|
|
856
688
|
* second fetch and keeps the two figures arithmetically consistent.
|
|
857
689
|
*/
|
|
858
690
|
snapshot?: FxRateSnapshot;
|
|
859
691
|
}): Promise<FiatDenomination>;
|
|
860
692
|
/**
|
|
861
|
-
* The mid-job ask's fiat pin, in the target currency
|
|
693
|
+
* The mid-job ask's fiat pin, in the target currency — what the cap
|
|
862
694
|
* on this job's cumulative draw growth is summed from, and what the top-up
|
|
863
695
|
* funds against.
|
|
864
696
|
*
|
|
865
697
|
* Two differences from {@link msatsToFiatMicro}, both scoped to the ask:
|
|
866
698
|
*
|
|
867
699
|
* - **A fiat-form ask already in the target currency is taken verbatim**, so a
|
|
868
|
-
*
|
|
869
|
-
*
|
|
870
|
-
*
|
|
871
|
-
*
|
|
872
|
-
*
|
|
700
|
+
* `ctx.requestPayment({ amount, currency })` keeps the exactness it has and
|
|
701
|
+
* never round-trips through a rate. Any other form converts `msats` on the
|
|
702
|
+
* caller's snapshot — including a fiat-form ask in some *other* currency,
|
|
703
|
+
* which converts on the very snapshot that sized its msats, so the pin and
|
|
704
|
+
* the wire ask can't disagree about the rate.
|
|
873
705
|
* - **Floors at one micro-unit rather than returning `undefined`.**
|
|
874
|
-
*
|
|
875
|
-
*
|
|
876
|
-
*
|
|
877
|
-
*
|
|
878
|
-
*
|
|
706
|
+
* `ctx.requestPayment(500)` is half a micro at $100k/BTC — a legitimate, if
|
|
707
|
+
* tiny, ask, and refusing to denominate it poisons the job's whole ask total
|
|
708
|
+
* and with it the ceiling. One micro is provably conservative for a ceiling:
|
|
709
|
+
* the ask is worth less than that. `resolvePriceFiat` and `msatsToFiatMicro`
|
|
710
|
+
* keep their sub-micro refusals, which are load-bearing on the upfront path.
|
|
879
711
|
*
|
|
880
712
|
* Throws only what `fxRateFor` throws — an unsupported target currency, or an
|
|
881
713
|
* `fx_rate_unavailable` for one the snapshot never carried. The ask path catches
|
|
882
|
-
* both and degrades to an unpinnable ask
|
|
714
|
+
* both and degrades to an unpinnable ask, naming which it was.
|
|
883
715
|
*/
|
|
884
716
|
declare function pinAskFiat(args: {
|
|
885
717
|
msats: number;
|
|
@@ -892,65 +724,29 @@ declare function pinAskFiat(args: {
|
|
|
892
724
|
}): PriceFiat | undefined;
|
|
893
725
|
/**
|
|
894
726
|
* Fiat micro-units actually funded for a rail payment of `paidMsats` against
|
|
895
|
-
* a job quoted at `requiredMsats` ↔ `priceFiat
|
|
896
|
-
*
|
|
727
|
+
* a job quoted at `requiredMsats` ↔ `priceFiat`. An overpayment funds and draws
|
|
728
|
+
* the full paid amount so ledger value matches money received.
|
|
897
729
|
* Pro-rata at the quote-pinned ratio, so the exact-payment case — what every
|
|
898
730
|
* conformant client sends — is `priceFiat.amountMicro` with zero drift.
|
|
899
731
|
*/
|
|
900
732
|
declare function fundedMicroFor(priceFiat: PriceFiat, paidMsats: number, requiredMsats: number): number;
|
|
901
733
|
/**
|
|
902
|
-
*
|
|
903
|
-
*
|
|
904
|
-
*
|
|
905
|
-
*
|
|
906
|
-
* stablecoin, and `priceFiat.amountMicro` is already an exact USD micro figure,
|
|
907
|
-
* so the advertisement is the price — no rate in the derivation, and therefore
|
|
908
|
-
* no way for the 402 and the submit to disagree about it.
|
|
909
|
-
*
|
|
910
|
-
* The BTC hop this replaces was not merely lossy, it was *unpinned*. The 402 is
|
|
911
|
-
* stateless, so the figure was recomputed at submit from whatever the fx cache
|
|
912
|
-
* held by then, through two rate-dependent roundings: `resolveStaticPriceMsats`
|
|
913
|
-
* ceils the price to a whole **satoshi**, and `msatsToUsdc` ceils that back to
|
|
914
|
-
* µUSDC. A rate refresh between the 402 and the payment therefore moved the
|
|
915
|
-
* reconstruction by up to a satoshi's worth (~$0.0013 at $100k/BTC) — and when
|
|
916
|
-
* it moved down, the caller's exact payment read as an overpayment, pro-rated
|
|
917
|
-
* above `price_micro`, and was refused `funding_commitment_mismatch` *after*
|
|
918
|
-
* the facilitator had settled. Money moved, no job, no credit. The sat-ceil
|
|
919
|
-
* also overcharged every x402 caller outright: at $0.01 and $97,432/BTC the
|
|
920
|
-
* quote is 11 sats and the old advertisement demanded 10,718 µUSDC — 7% above
|
|
921
|
-
* the price the caller was quoted and the ledger recorded.
|
|
922
|
-
*
|
|
923
|
-
* Every caller gates x402 on a USD-priced DVM before reaching this helper.
|
|
924
|
-
* Keeping the helper USD-only makes a future advertisement call site fail
|
|
925
|
-
* closed instead of quietly restoring the rate-derived non-USD path.
|
|
734
|
+
* USD micro-units are already USDC's six-decimal units. Advertise them verbatim
|
|
735
|
+
* and value settlement against the same figure; a BTC conversion would add
|
|
736
|
+
* rounding and an unpinned rate between challenge and settlement.
|
|
737
|
+
* Non-USD callers fail closed rather than implicitly introducing that FX hop.
|
|
926
738
|
*/
|
|
927
739
|
declare function x402RequiredUsdcMicro(priceFiat: PriceFiat): number;
|
|
928
740
|
/**
|
|
929
|
-
*
|
|
930
|
-
*
|
|
931
|
-
*
|
|
932
|
-
*
|
|
933
|
-
* msat quote, so the two frames can never disagree about what landed.
|
|
934
|
-
*
|
|
935
|
-
* x402 must NOT reach {@link fundedMicroFor} through `usdcToMsats`. The
|
|
936
|
-
* advertised figure is stated in µUSDC by {@link x402RequiredUsdcMicro} — the
|
|
937
|
-
* price verbatim for a USD job — and the
|
|
938
|
-
* receipt converts back with `usdcToMsats`, a `Math.round`, which is the
|
|
939
|
-
* inverse of neither. The round trip lands at or above `requiredMsats`, which
|
|
940
|
-
* took the pro-rata branch and valued a payment of exactly the advertised
|
|
941
|
-
* amount above `price_micro`. That mismatch used to be refused only after the
|
|
942
|
-
* facilitator had broadcast the transfer, so a conformant caller signing the
|
|
943
|
-
* quoted price lost the payment outright (the gate now also runs pre-settle —
|
|
944
|
-
* internal-review).
|
|
945
|
-
*
|
|
946
|
-
* Valuing in the rail's own units removes the manufactured disagreement and
|
|
947
|
-
* nothing else: a genuine overpayment still pro-rates, and both inputs are on
|
|
948
|
-
* the 402, so the caller can derive the figure it must sign from that alone.
|
|
741
|
+
* Value a settled authorization against the advertised USDC amount, preserving
|
|
742
|
+
* the quote at exact payment and prorating genuine over/underpayment. Use this
|
|
743
|
+
* ratio for both fiat and msat frames: a USDC-to-msat round trip can round an
|
|
744
|
+
* exact payment into a false overpayment and invalidate its signed commitment.
|
|
949
745
|
*/
|
|
950
746
|
declare function x402SettledShare(quoted: number, requiredUsdcMicro: number, settledUsdcMicro: number): number;
|
|
951
747
|
/**
|
|
952
748
|
* SHA-256 hex over the raw funding artifact — the value an explicit
|
|
953
|
-
* fund-and-draw request must sign as `fund.commitment` (
|
|
749
|
+
* fund-and-draw request must sign as `fund.commitment` (the funding-commitment rule).
|
|
954
750
|
* The artifact is the exact header-borne string: the `X-Cashu` header value,
|
|
955
751
|
* the `X-PAYMENT` header value, or the extracted `Payment <…>` mpp scheme
|
|
956
752
|
* string. Hashing the verbatim wire string keeps the commitment computable by
|
|
@@ -964,14 +760,14 @@ declare function fundingCommitment(artifact: string): string;
|
|
|
964
760
|
declare function implicitCreditId(rail: "cashu" | "x402" | "tempo", paymentId: string): string;
|
|
965
761
|
/**
|
|
966
762
|
* Whether a credit id sits in a namespace the server derives — which a caller
|
|
967
|
-
* may **reference** but must never **create
|
|
763
|
+
* may **reference** but must never **create**.
|
|
968
764
|
*
|
|
969
765
|
* Both namespaces are computable by a third party: `imp:` from the rail's
|
|
970
766
|
* payment id, `fnd:` from `(pubkey, fund_id)`. `CreditLedger.fund` INSERTs at
|
|
971
767
|
* whatever id it is handed, so without this rule anyone could pre-open a
|
|
972
768
|
* credit at an id the server would later derive for someone else. The victim's
|
|
973
769
|
* top-up then settles on the rail before `fund` refuses with `caller_mismatch`
|
|
974
|
-
*
|
|
770
|
+
* they pay and hold nothing. Keeping creation server-side means a derived id
|
|
975
771
|
* is only ever owned by the caller it was derived for.
|
|
976
772
|
*
|
|
977
773
|
* Referencing stays open, and has to: the id of a credit opened this way is
|
|
@@ -981,7 +777,7 @@ declare function implicitCreditId(rail: "cashu" | "x402" | "tempo", paymentId: s
|
|
|
981
777
|
declare function isDerivedCreditId(creditId: string): boolean;
|
|
982
778
|
/**
|
|
983
779
|
* Deterministic credit id for a `/v1/credit` top-up that names no credit —
|
|
984
|
-
* the documented "omit `credit_id` to open a new one" flow
|
|
780
|
+
* the documented "omit `credit_id` to open a new one" flow.
|
|
985
781
|
*
|
|
986
782
|
* Minting a fresh random id per attempt would make that flow the one top-up
|
|
987
783
|
* shape that is *not* retry-safe: the second attempt looks up
|
|
@@ -998,8 +794,8 @@ declare function isDerivedCreditId(creditId: string): boolean;
|
|
|
998
794
|
*/
|
|
999
795
|
declare function derivedFundCreditId(callerPubkey: string, fundId: string): string;
|
|
1000
796
|
/**
|
|
1001
|
-
* Settlement reference stamped on the revenue event of an **explicit** draw
|
|
1002
|
-
*
|
|
797
|
+
* Settlement reference stamped on the revenue event of an **explicit** draw;
|
|
798
|
+
* one funding backs N draws, so the funding's own rail reference
|
|
1003
799
|
* cannot key them: `revenue_events` carries a global `UNIQUE (rail, tx_hash)`
|
|
1004
800
|
* that deliberately drops cross-job reuse of a settlement reference, and
|
|
1005
801
|
* reusing it would silently discard every draw but the first.
|
|
@@ -1012,18 +808,8 @@ declare function derivedFundCreditId(callerPubkey: string, fundId: string): stri
|
|
|
1012
808
|
declare function drawSettlementRef(dvmId: string, creditId: string, drawId: string): string;
|
|
1013
809
|
|
|
1014
810
|
/**
|
|
1015
|
-
*
|
|
1016
|
-
*
|
|
1017
|
-
* commit point of their own — the money moves at the facilitator / the payer's
|
|
1018
|
-
* mpp server — so the marker row, inserted in the SAME transaction as
|
|
1019
|
-
* `CreditLedger.fund` + `draw`, is what makes a crash-replayed credential
|
|
1020
|
-
* unable to fund twice: the second attempt collides on the primary key and
|
|
1021
|
-
* surfaces as {@link ProcessedPaymentReplayError}, which the verifier routes
|
|
1022
|
-
* into the internal-review recovery gates instead of a second fund.
|
|
1023
|
-
*
|
|
1024
|
-
* The Cashu rail does not use this store — its accumulator insert is already
|
|
1025
|
-
* the durable commit point, and its replay key (the `X-Cashu-Request-Id`
|
|
1026
|
-
* regime) must stay exactly as-is (dual-idempotency rule, spec §2).
|
|
811
|
+
* x402/Tempo marker, funding and draw commit in one transaction so a retry
|
|
812
|
+
* cannot fund twice. Cashu retains its separate accumulator replay regime.
|
|
1027
813
|
*/
|
|
1028
814
|
interface ProcessedPaymentStore {
|
|
1029
815
|
/**
|
|
@@ -1049,11 +835,8 @@ interface ProcessedPaymentStore {
|
|
|
1049
835
|
paymentId: string;
|
|
1050
836
|
}): Promise<ProcessedPaymentRecord | undefined>;
|
|
1051
837
|
/**
|
|
1052
|
-
*
|
|
1053
|
-
*
|
|
1054
|
-
* the handle through to `record` / `CreditLedger.fund` / `draw` so all
|
|
1055
|
-
* three commit atomically. The memory implementation invokes `fn` with no
|
|
1056
|
-
* handle — its operations are synchronous-atomic in-process.
|
|
838
|
+
* Postgres supplies a transaction for marker, fund and draw; failure rolls it
|
|
839
|
+
* back. Memory invokes fn without a transactional handle or rollback.
|
|
1057
840
|
*/
|
|
1058
841
|
withTx<T>(fn: (tx?: ProcessedPaymentQuerier) => Promise<T>): Promise<T>;
|
|
1059
842
|
}
|
|
@@ -1086,7 +869,7 @@ declare class PostgresProcessedPaymentStore implements ProcessedPaymentStore {
|
|
|
1086
869
|
withTx<T>(fn: (tx?: ProcessedPaymentQuerier) => Promise<T>): Promise<T>;
|
|
1087
870
|
/** Create the `processed_payments` table if absent. Call once at SDK boot. */
|
|
1088
871
|
init(): Promise<void>;
|
|
1089
|
-
/** The boot DDL itself — always runs under {@link withSdkInitLock}
|
|
872
|
+
/** The boot DDL itself — always runs under {@link withSdkInitLock}. */
|
|
1090
873
|
private createTables;
|
|
1091
874
|
record(args: {
|
|
1092
875
|
dvmId: string;
|
|
@@ -1128,11 +911,10 @@ declare class MemoryProcessedPaymentStore implements ProcessedPaymentStore {
|
|
|
1128
911
|
/**
|
|
1129
912
|
* Machine codes an accumulator-path receive can reject with. Named and exported
|
|
1130
913
|
* so the payment-error builder in `payment.ts` folds them into `PaymentErrorCode`
|
|
1131
|
-
* and keeps display/hint/retryable copy in lockstep with this union
|
|
914
|
+
* and keeps display/hint/retryable copy in lockstep with this union.
|
|
1132
915
|
*/
|
|
1133
916
|
type AccumulatorRejectionCode = "missing_request_id" | "missing_lock_pubkey" | "not_p2pk_locked" | "wrong_lock_pubkey" | "proof_not_unspent" | "proof_pending" | "replay_rejected" | "mint_unreachable" | "invalid_token";
|
|
1134
917
|
|
|
1135
|
-
/** Durable state of one exact x402 authorization. */
|
|
1136
918
|
type X402ExactSettlementStatus = "prepared" | "submitting" | "settled" | "local_committed" | "ambiguous" | "rejected" | "complete";
|
|
1137
919
|
/** Serializable local economic effect that follows an exact settlement. */
|
|
1138
920
|
interface X402ExactSettlementEffect {
|
|
@@ -1181,7 +963,6 @@ interface X402ExactAuthorizationFacts {
|
|
|
1181
963
|
nonce: string;
|
|
1182
964
|
fingerprint: string;
|
|
1183
965
|
}
|
|
1184
|
-
/** One durable exact-x402 settlement intent. */
|
|
1185
966
|
interface X402ExactSettlementIntent extends X402ExactAuthorizationFacts {
|
|
1186
967
|
id: string;
|
|
1187
968
|
dvmId: string;
|
|
@@ -1236,11 +1017,9 @@ interface X402ExactSettlementStore {
|
|
|
1236
1017
|
}): Promise<X402ExactSettlementIntent[]>;
|
|
1237
1018
|
attempts(dvmId: string, intentId: string): Promise<X402ExactSettlementAttempt[]>;
|
|
1238
1019
|
}
|
|
1239
|
-
/** Postgres-backed exact settlement intent store. */
|
|
1240
1020
|
declare class PostgresX402ExactSettlementStore implements X402ExactSettlementStore {
|
|
1241
1021
|
private readonly pool;
|
|
1242
1022
|
constructor(pool: Pick<Pool, "query" | "connect">);
|
|
1243
|
-
/** Create the exact settlement tables. */
|
|
1244
1023
|
init(): Promise<void>;
|
|
1245
1024
|
prepare(intent: Omit<X402ExactSettlementIntent, "status" | "createdAt" | "updatedAt" | "resolvedAt">): Promise<X402ExactSettlementIntent>;
|
|
1246
1025
|
get(dvmId: string, intentId: string): Promise<X402ExactSettlementIntent | undefined>;
|
|
@@ -1349,7 +1128,6 @@ type X402ExactAcceptance<T> = {
|
|
|
1349
1128
|
intentId: string;
|
|
1350
1129
|
reason: string;
|
|
1351
1130
|
};
|
|
1352
|
-
/** Dependencies for the durable exact x402 settlement coordinator. */
|
|
1353
1131
|
interface X402ExactSettlementServerOpts {
|
|
1354
1132
|
store: X402ExactSettlementStore;
|
|
1355
1133
|
evidence?: X402ExactSettlementEvidenceReader;
|
|
@@ -1372,13 +1150,11 @@ declare class X402ExactSettlementServer {
|
|
|
1372
1150
|
effect?: X402ExactSettlementEffect;
|
|
1373
1151
|
commit: (receipt: X402Receipt, tx?: ProcessedPaymentQuerier, effect?: X402ExactSettlementEffect) => Promise<T>;
|
|
1374
1152
|
}): Promise<X402ExactAcceptance<T>>;
|
|
1375
|
-
/** Read the operator queue. */
|
|
1376
1153
|
list(args: {
|
|
1377
1154
|
dvmId: string;
|
|
1378
1155
|
limit: number;
|
|
1379
1156
|
includeResolved?: boolean;
|
|
1380
1157
|
}): Promise<X402ExactSettlementIntent[]>;
|
|
1381
|
-
/** Read durable attempt evidence for one intent. */
|
|
1382
1158
|
attempts(dvmId: string, intentId: string): Promise<X402ExactSettlementAttempt[]>;
|
|
1383
1159
|
/** Reconcile and finish one intent from chain evidence. */
|
|
1384
1160
|
reconcile(dvmId: string, intentId: string): Promise<{
|
|
@@ -1437,7 +1213,7 @@ interface X402BatchChannelObservation {
|
|
|
1437
1213
|
refundNonce: number;
|
|
1438
1214
|
}
|
|
1439
1215
|
/**
|
|
1440
|
-
* The chain submission behind an accepted voucher did not return success
|
|
1216
|
+
* The chain submission behind an accepted voucher did not return success.
|
|
1441
1217
|
*
|
|
1442
1218
|
* Typed and `retryable` because it is a service-availability fault, not a fault
|
|
1443
1219
|
* in the credential: the voucher verified, the ledger leg rolled back, and the
|
|
@@ -1479,16 +1255,16 @@ interface X402BatchRefusal {
|
|
|
1479
1255
|
* `false` when re-signing cannot fix this: the caller must be answered with a
|
|
1480
1256
|
* terminal error rather than a 402 carrying fresh requirements, because an
|
|
1481
1257
|
* agent reads a corrective 402 as "sign this and resubmit" and lands straight
|
|
1482
|
-
* back on the same refusal
|
|
1258
|
+
* back on the same refusal. Omitted means corrective.
|
|
1483
1259
|
*/
|
|
1484
1260
|
corrective?: boolean;
|
|
1485
1261
|
}
|
|
1486
1262
|
/**
|
|
1487
|
-
* What the chain says about one wedged settlement
|
|
1263
|
+
* What the chain says about one wedged settlement.
|
|
1488
1264
|
*
|
|
1489
1265
|
* The four answers are kept apart because their repairs differ and because an
|
|
1490
1266
|
* operator re-pointing an RPC needs a definitive answer to read as definitive
|
|
1491
|
-
*
|
|
1267
|
+
* the same split `LightningReceive.lookupSettlement` draws between
|
|
1492
1268
|
* `invoice_unknown_to_wallet` and `wallet_unreachable`. `findAppliedSettlement`
|
|
1493
1269
|
* collapses all of them (`undefined` for absent *and* unbookmarked, a throw for
|
|
1494
1270
|
* transport), so this is where they are separated.
|
|
@@ -1563,7 +1339,7 @@ interface X402SettlementRepaired<T> {
|
|
|
1563
1339
|
}
|
|
1564
1340
|
/**
|
|
1565
1341
|
* The operator's exit from a settlement stuck between "chain paid" and "ledger
|
|
1566
|
-
* booked"
|
|
1342
|
+
* booked".
|
|
1567
1343
|
*
|
|
1568
1344
|
* `reconcile` is the ordinary recovery path with the chain's figure substituted
|
|
1569
1345
|
* for the facilitator's: the amount is re-derived from
|
|
@@ -1617,7 +1393,7 @@ interface X402BatchSettlementServer {
|
|
|
1617
1393
|
* Required on `refund`: the credit's unspent balance in the channel token's
|
|
1618
1394
|
* own units. It is the ceiling the on-chain payout is sized to, so that a
|
|
1619
1395
|
* drain returns what the caller is owed and nothing the DVM has earned
|
|
1620
|
-
*
|
|
1396
|
+
* `commit` must re-assert it against the authoritative ledger
|
|
1621
1397
|
* figure, since this one is read before the channel lock is taken.
|
|
1622
1398
|
*/
|
|
1623
1399
|
refundBoundNative?: number;
|
|
@@ -1633,7 +1409,7 @@ interface X402BatchSettlementServer {
|
|
|
1633
1409
|
* required in the *type*, because the payload the payer presents is
|
|
1634
1410
|
* what revokes, settles and pays: a refund voucher naming another
|
|
1635
1411
|
* channel would extinguish this credit's balance out of that channel's
|
|
1636
|
-
* escrow
|
|
1412
|
+
* escrow. The fund path binds in the ledger
|
|
1637
1413
|
* (`credit-ledger.ts`'s `rail_mismatch`) and the Tempo close binds in
|
|
1638
1414
|
* its credential verifier; this is x402's.
|
|
1639
1415
|
*/
|
|
@@ -1648,20 +1424,19 @@ interface X402BatchSettlementServer {
|
|
|
1648
1424
|
claimAndSettle(): Promise<void>;
|
|
1649
1425
|
/**
|
|
1650
1426
|
* Operator repair for a settlement wedged between chain and ledger
|
|
1651
|
-
*
|
|
1427
|
+
* Absent without durable storage: with no settlement rows there
|
|
1652
1428
|
* is no queue and nothing a repair could act on.
|
|
1653
1429
|
*/
|
|
1654
1430
|
repair?: X402SettlementRepair;
|
|
1655
1431
|
/**
|
|
1656
1432
|
* The durable settlement read the credit ledger gates spending on
|
|
1657
|
-
*
|
|
1433
|
+
* Absent without durable storage, on the same reasoning as
|
|
1658
1434
|
* {@link repair}: with no settlement rows nothing can be wedged, so there is
|
|
1659
1435
|
* nothing to gate.
|
|
1660
1436
|
*/
|
|
1661
1437
|
settlementGate?: X402RefundSettlementGate;
|
|
1662
1438
|
stop(): Promise<void>;
|
|
1663
1439
|
}
|
|
1664
|
-
/** Options for constructing one DVM's batch-settlement server. */
|
|
1665
1440
|
interface CreateX402BatchSettlementServerOpts {
|
|
1666
1441
|
network: string;
|
|
1667
1442
|
payTo: string;
|
|
@@ -1677,12 +1452,12 @@ interface CreateX402BatchSettlementServerOpts {
|
|
|
1677
1452
|
rpcUrl?: string;
|
|
1678
1453
|
db?: Pool;
|
|
1679
1454
|
/**
|
|
1680
|
-
* Durable store to adopt instead of building one from {@link db}
|
|
1455
|
+
* Durable store to adopt instead of building one from {@link db}.
|
|
1681
1456
|
*
|
|
1682
1457
|
* `createDVMServer` builds it at ledger construction, because the credit
|
|
1683
1458
|
* ledger's spend gate has to exist whether or not this server is ever
|
|
1684
1459
|
* constructed. Adopting it here keeps one `PostgresX402ChannelStorage` — and
|
|
1685
|
-
* so one settlement pool — per mount, which is what the
|
|
1460
|
+
* so one settlement pool — per mount, which is what the reasoning on
|
|
1686
1461
|
* {@link PostgresX402ChannelStorage} rests on. Ignored when the config
|
|
1687
1462
|
* supplies its own {@link X402BatchSettlementConfig.storage}.
|
|
1688
1463
|
*/
|
|
@@ -1694,7 +1469,7 @@ interface CreateX402BatchSettlementServerOpts {
|
|
|
1694
1469
|
* `undefined` means the channel's credit could not be resolved.
|
|
1695
1470
|
*
|
|
1696
1471
|
* Required, not optional: it is the ceiling every claim is capped at
|
|
1697
|
-
*
|
|
1472
|
+
* and a server that cannot derive one must claim nothing rather
|
|
1698
1473
|
* than fall back to the deposit.
|
|
1699
1474
|
*/
|
|
1700
1475
|
earnedNative: (channelId: string) => Promise<number | undefined>;
|
|
@@ -1705,13 +1480,13 @@ interface CreateX402BatchSettlementServerOpts {
|
|
|
1705
1480
|
/** Authoritative channel-view override for deterministic tests. */
|
|
1706
1481
|
channelStateReader?: (channelId: string) => Promise<X402BatchChannelObservation>;
|
|
1707
1482
|
/**
|
|
1708
|
-
* The payout tracker
|
|
1483
|
+
* The payout tracker. Attached with this server's settle scope
|
|
1709
1484
|
* and routed every claim and settle, so the batch that lands at `pay_to` is
|
|
1710
1485
|
* reported with the value claimed into it, and the pending snapshot can say
|
|
1711
1486
|
* what is still on the channels and whether the settle is wedged.
|
|
1712
1487
|
*
|
|
1713
1488
|
* @internal Wired by `createDVMServer` from the platform reporter; not a
|
|
1714
|
-
* builder surface, and not part of the extractable SDK contract
|
|
1489
|
+
* builder surface, and not part of the extractable SDK contract.
|
|
1715
1490
|
*/
|
|
1716
1491
|
payout?: X402PayoutObserver;
|
|
1717
1492
|
}
|
|
@@ -1723,12 +1498,11 @@ interface CreateX402BatchSettlementServerOpts {
|
|
|
1723
1498
|
*/
|
|
1724
1499
|
declare function createX402BatchSettlementServer(opts: CreateX402BatchSettlementServerOpts): Promise<X402BatchSettlementServer>;
|
|
1725
1500
|
|
|
1726
|
-
/** Payment state from upfront token verification. */
|
|
1727
1501
|
interface PaymentInfo {
|
|
1728
1502
|
/**
|
|
1729
1503
|
* Rail value this payment contributes to the job, in millisatoshis.
|
|
1730
1504
|
*
|
|
1731
|
-
* For a credit-drawn job
|
|
1505
|
+
* For a credit-drawn job this is the **draw's** allocated share of
|
|
1732
1506
|
* the credit's rail value, not the amount the caller handed over on this
|
|
1733
1507
|
* request — under an explicit fund-and-draw the caller may have funded twenty
|
|
1734
1508
|
* jobs' worth. That makes it the revenue basis for this job: mid-job top-ups
|
|
@@ -1739,38 +1513,36 @@ interface PaymentInfo {
|
|
|
1739
1513
|
/**
|
|
1740
1514
|
* Rail that satisfied the upfront payment, if any. A {@link FundingMethod}
|
|
1741
1515
|
* rather than a `PaymentMethod`: a draw against a Lightning-funded credit
|
|
1742
|
-
* reports `"lightning"` here
|
|
1516
|
+
* reports `"lightning"` here, which is what lets the revenue
|
|
1743
1517
|
* ledger book it instead of silently dropping the row.
|
|
1744
1518
|
*/
|
|
1745
1519
|
paymentRail?: FundingMethod;
|
|
1746
1520
|
receivedProofs: ProofLike[];
|
|
1747
1521
|
/**
|
|
1748
1522
|
* Settlement reference for the credit. x402 carries the EVM tx hash from
|
|
1749
|
-
* the facilitator
|
|
1750
|
-
* HMAC-bound to the realm so cross-DVM collisions are impossible
|
|
1751
|
-
* cashu accumulator
|
|
1752
|
-
*
|
|
1523
|
+
* the facilitator; mpp carries the credential's `challenge.id`,
|
|
1524
|
+
* HMAC-bound to the realm so cross-DVM collisions are impossible;
|
|
1525
|
+
* cashu accumulator sets it to the `X-Cashu-Request-Id` UUID
|
|
1526
|
+
* Required by the platform revenue ledger for `tempo`/`x402`
|
|
1753
1527
|
* rails; nullable for cashu's `tx_hash` column.
|
|
1754
1528
|
*/
|
|
1755
1529
|
paymentTxHash?: string;
|
|
1756
1530
|
/** Chain transaction returned to a caller recovering this accepted request. */
|
|
1757
1531
|
paymentTransactionHash?: string;
|
|
1758
1532
|
/**
|
|
1759
|
-
*
|
|
1760
|
-
*
|
|
1761
|
-
* `paidMsats / 1000` derivation in the platform callback covers it.
|
|
1533
|
+
* Stablecoin micro-units for Tempo and x402. Cashu leaves this unset; its revenue is derived
|
|
1534
|
+
* from paidMsats.
|
|
1762
1535
|
*/
|
|
1763
1536
|
nativeAmount?: number;
|
|
1764
|
-
/** Native asset tag matching `revenue_events.native_asset` (internal-review). */
|
|
1765
1537
|
nativeAsset?: "sats" | "usdc" | "usdc.e" | "usd-cents";
|
|
1766
1538
|
/**
|
|
1767
1539
|
* Discriminator written into `revenue_events.metadata.cashu_flow` so ops
|
|
1768
|
-
* queries can split per-call cashu rows by source path
|
|
1540
|
+
* queries can split per-call cashu rows by source path.
|
|
1769
1541
|
*/
|
|
1770
1542
|
cashuFlow?: "p2pk_accumulator";
|
|
1771
1543
|
/**
|
|
1772
1544
|
* Base64-encoded x402 `SettleResponse` for the `X-PAYMENT-RESPONSE`
|
|
1773
|
-
* response header
|
|
1545
|
+
* response header. Set on the upfront-flow x402 success path;
|
|
1774
1546
|
* `app.ts` attaches it to the 2xx response per x402 spec.
|
|
1775
1547
|
*/
|
|
1776
1548
|
x402SettleResponseHeader?: string;
|
|
@@ -1781,21 +1553,21 @@ interface PaymentInfo {
|
|
|
1781
1553
|
replayed: boolean;
|
|
1782
1554
|
};
|
|
1783
1555
|
/**
|
|
1784
|
-
* Credit-ledger linkage
|
|
1556
|
+
* Credit-ledger linkage. Set whenever the payment funded and/or
|
|
1785
1557
|
* drew the ledger — every paid job with a ledger wired. Persisted on the
|
|
1786
1558
|
* job so the terminal funnel can settle/release the draw and the receipt
|
|
1787
1559
|
* can countersign the `ReceiptCredit` block.
|
|
1788
1560
|
*/
|
|
1789
1561
|
creditId?: string;
|
|
1790
|
-
/** The draw placed for this job on `creditId
|
|
1562
|
+
/** The draw placed for this job on `creditId`. */
|
|
1791
1563
|
drawId?: string;
|
|
1792
|
-
/** The draw's fiat amount, 1e-6 of {@link creditCurrency}
|
|
1564
|
+
/** The draw's fiat amount, 1e-6 of {@link creditCurrency}. */
|
|
1793
1565
|
drawAmountMicro?: number;
|
|
1794
|
-
/** Currency the credit — and therefore the draw — is denominated in
|
|
1566
|
+
/** Currency the credit — and therefore the draw — is denominated in. */
|
|
1795
1567
|
creditCurrency?: string;
|
|
1796
1568
|
/**
|
|
1797
1569
|
* The funding leg, when this request actually moved money onto a credit
|
|
1798
|
-
*
|
|
1570
|
+
* Reported to the platform as a **deposit**: a liability until
|
|
1799
1571
|
* drawn, never summed into revenue. Absent on a pure draw against an
|
|
1800
1572
|
* existing balance.
|
|
1801
1573
|
*/
|
|
@@ -1805,7 +1577,7 @@ interface PaymentInfo {
|
|
|
1805
1577
|
fundingCredit?: CreditSnapshot;
|
|
1806
1578
|
/**
|
|
1807
1579
|
* Fiat micro of {@link creditCurrency} this payment funded that bought the
|
|
1808
|
-
* job **nothing**
|
|
1580
|
+
* job **nothing** — a second payment for an ask the job's draw
|
|
1809
1581
|
* already covers, or the overpaid tail of an oversized one. It is available
|
|
1810
1582
|
* balance on {@link creditId}, drainable at once, and the caller has to be
|
|
1811
1583
|
* told or they will re-send. Absent on every ordinary payment.
|
|
@@ -1813,7 +1585,7 @@ interface PaymentInfo {
|
|
|
1813
1585
|
unappliedMicro?: number;
|
|
1814
1586
|
}
|
|
1815
1587
|
/**
|
|
1816
|
-
* A funding event as the platform books it — one deposit row (
|
|
1588
|
+
* A funding event as the platform books it — one deposit row (spec
|
|
1817
1589
|
* §10). Keyed on `(dvmId, creditId, fundingId)`, so a retried report is a
|
|
1818
1590
|
* no-op rather than a double-count.
|
|
1819
1591
|
*/
|
|
@@ -1845,12 +1617,12 @@ interface CreditFundingReport {
|
|
|
1845
1617
|
channelId: string;
|
|
1846
1618
|
cumulativeAmount: string;
|
|
1847
1619
|
};
|
|
1848
|
-
/** TIP-1034 chain binding, reported so the platform can route a close
|
|
1620
|
+
/** TIP-1034 chain binding, reported so the platform can route a close. */
|
|
1849
1621
|
tempoChannel?: TempoChannelReport;
|
|
1850
1622
|
}
|
|
1851
1623
|
/**
|
|
1852
1624
|
* The chain identity of a TIP-1034 channel, as reported to the platform
|
|
1853
|
-
* alongside its deposit
|
|
1625
|
+
* alongside its deposit. Read from the DVM's own durable channel
|
|
1854
1626
|
* store, never from the caller's credential.
|
|
1855
1627
|
*/
|
|
1856
1628
|
interface TempoChannelReport {
|
|
@@ -1858,7 +1630,6 @@ interface TempoChannelReport {
|
|
|
1858
1630
|
escrow: string;
|
|
1859
1631
|
payee?: string;
|
|
1860
1632
|
}
|
|
1861
|
-
/** Options for verifying an upfront payment on job submission. */
|
|
1862
1633
|
interface UpfrontPaymentOpts {
|
|
1863
1634
|
cashuToken?: string;
|
|
1864
1635
|
requiredMsats?: number;
|
|
@@ -1875,7 +1646,7 @@ interface UpfrontPaymentOpts {
|
|
|
1875
1646
|
/**
|
|
1876
1647
|
* Resource URL the client retried against. Bound into the rebuilt
|
|
1877
1648
|
* `PaymentRequirements` when verifying x402, and surfaced on the 402 body
|
|
1878
|
-
* `accepts` array for x402 dual-serve
|
|
1649
|
+
* `accepts` array for x402 dual-serve.
|
|
1879
1650
|
*/
|
|
1880
1651
|
x402Resource?: string;
|
|
1881
1652
|
/** Either the structured credential object or its serialized `Authorization: Payment <…>` value. */
|
|
@@ -1884,17 +1655,17 @@ interface UpfrontPaymentOpts {
|
|
|
1884
1655
|
/**
|
|
1885
1656
|
* Request path of the resource being paid for (e.g. `/v1/job`). Bound into
|
|
1886
1657
|
* each issued mppx challenge's `meta.path` so a credential can't be replayed
|
|
1887
|
-
* against a different paid endpoint of the same DVM
|
|
1658
|
+
* against a different paid endpoint of the same DVM.
|
|
1888
1659
|
*/
|
|
1889
1660
|
resourcePath?: string;
|
|
1890
1661
|
/**
|
|
1891
1662
|
* Whether `tempo/session` may be issued and newly accepted right now — the
|
|
1892
|
-
* platform close-observer and hosted settlement-readiness gates
|
|
1893
|
-
*
|
|
1663
|
+
* platform close-observer and hosted settlement-readiness gates, resolved per
|
|
1664
|
+
* request by `DVMServer`. Absent means offered: a
|
|
1894
1665
|
* self-hosted DVM operates these responsibilities itself.
|
|
1895
1666
|
*
|
|
1896
1667
|
* `false` refuses a session credential that would open a channel the platform
|
|
1897
|
-
* cannot safely protect or settle
|
|
1668
|
+
* cannot safely protect or settle, and withholds session
|
|
1898
1669
|
* challenges from every 402 this call emits, *except* on a credit already
|
|
1899
1670
|
* bound to a TIP-1034 channel. That deposit is already exposed, so refusing
|
|
1900
1671
|
* its vouchers only strands it; and since a channel-bound credit can be funded
|
|
@@ -1907,11 +1678,10 @@ interface UpfrontPaymentOpts {
|
|
|
1907
1678
|
/**
|
|
1908
1679
|
* Tracks consumed `(realm, challenge.id)` pairs so a credential that was
|
|
1909
1680
|
* already accepted on the upfront path can't be replayed within its
|
|
1910
|
-
* `expires` window
|
|
1681
|
+
* `expires` window. Optional: when absent, the upfront flow has
|
|
1911
1682
|
* no per-process replay protection beyond mppx's own expiry check.
|
|
1912
1683
|
*/
|
|
1913
1684
|
consumedCredentialStore?: ConsumedCredentialStore;
|
|
1914
|
-
/** Cashu receive mode (internal-review / internal-review). Routes the receive path. */
|
|
1915
1685
|
cashuMode?: CashuMode;
|
|
1916
1686
|
/** Per-DVM Postgres pool — required for the `p2pk-accumulator` path. */
|
|
1917
1687
|
accumulatorDb?: AccumulatorPool;
|
|
@@ -1921,7 +1691,7 @@ interface UpfrontPaymentOpts {
|
|
|
1921
1691
|
/**
|
|
1922
1692
|
* Active NUT-11 P2PK lock pubkeys — required for the `p2pk-accumulator`
|
|
1923
1693
|
* path. Includes the current pubkey followed by retired pubkeys still
|
|
1924
|
-
* within the rotation grace window
|
|
1694
|
+
* within the rotation grace window. Caller resolves via
|
|
1925
1695
|
* `loadLockPubkeyState` per-request so rotations propagate without a
|
|
1926
1696
|
* server restart.
|
|
1927
1697
|
*/
|
|
@@ -1929,7 +1699,7 @@ interface UpfrontPaymentOpts {
|
|
|
1929
1699
|
/** UUIDv4 from `X-Cashu-Request-Id` header. */
|
|
1930
1700
|
requestId?: string;
|
|
1931
1701
|
/**
|
|
1932
|
-
* Runtime mint-health tracker
|
|
1702
|
+
* Runtime mint-health tracker. Receive path short-circuits with
|
|
1933
1703
|
* `cashu_mint_sick` when the token's mint is currently flagged sick, so
|
|
1934
1704
|
* agents retry against a healthy alternative without waiting for the
|
|
1935
1705
|
* per-call mint timeout to fire inside `acceptAccumulatorPayment`.
|
|
@@ -1937,37 +1707,34 @@ interface UpfrontPaymentOpts {
|
|
|
1937
1707
|
mintHealthTracker?: MintHealthTracker;
|
|
1938
1708
|
/**
|
|
1939
1709
|
* Host fx fetcher used to price the non-sat rails (x402 USDC, mppx fiat)
|
|
1940
|
-
* through the same BTC/USD source the quote path uses
|
|
1710
|
+
* through the same BTC/USD source the quote path uses. On a hosted
|
|
1941
1711
|
* DVM this is the platform-served snapshot; self-hosted defaults to
|
|
1942
1712
|
* CoinGecko-direct. Optional: a lazy default fetcher stands in when unset, so
|
|
1943
1713
|
* the payment path never reaches `fetchBtcUsdRate`.
|
|
1944
1714
|
*/
|
|
1945
1715
|
fxFetcher?: FxFetcher;
|
|
1946
1716
|
/**
|
|
1947
|
-
*
|
|
1948
|
-
*
|
|
1949
|
-
*
|
|
1950
|
-
* when the price can't be fiat-denominated), the payment is accepted with
|
|
1951
|
-
* legacy semantics and a structured warning — money is never refused
|
|
1952
|
-
* because the ledger is unavailable.
|
|
1717
|
+
* Attached payments fund and draw an implicit per-job credit atomically with
|
|
1718
|
+
* rail accounting. Omitting the ledger uses the legacy ledgerless path;
|
|
1719
|
+
* a configured ledger requires a fiat-denominated price before acceptance.
|
|
1953
1720
|
*/
|
|
1954
1721
|
creditLedger?: CreditLedgerLike;
|
|
1955
1722
|
/**
|
|
1956
|
-
* Durable processed-payment markers for the x402/mpp rails
|
|
1723
|
+
* Durable processed-payment markers for the x402/mpp rails.
|
|
1957
1724
|
* Those rails commit money outside dvmkit, so the marker row — written in
|
|
1958
1725
|
* the same transaction as the ledger fund — is what makes a crash-replayed
|
|
1959
1726
|
* credential unable to fund twice.
|
|
1960
1727
|
*/
|
|
1961
1728
|
processedPayments?: ProcessedPaymentStore;
|
|
1962
1729
|
/**
|
|
1963
|
-
* Reporter-owned transactional outbox enqueue
|
|
1730
|
+
* Reporter-owned transactional outbox enqueue. When present,
|
|
1964
1731
|
* every funding awaits this inside the rail's commit transaction, so money
|
|
1965
1732
|
* and its durable deposit report land together.
|
|
1966
1733
|
*/
|
|
1967
1734
|
enqueueCreditDeposit?: CreditDepositEnqueue;
|
|
1968
1735
|
/**
|
|
1969
1736
|
* The job price's fiat denomination, pinned by the caller from the same
|
|
1970
|
-
* quote surface that produced `requiredMsats
|
|
1737
|
+
* quote surface that produced `requiredMsats`. The ledger is
|
|
1971
1738
|
* fiat-micro denominated; fund = draw = this amount in the exact-payment
|
|
1972
1739
|
* case, so no fresh fx conversion happens on the verify hot path.
|
|
1973
1740
|
*/
|
|
@@ -1983,14 +1750,14 @@ interface UpfrontPaymentOpts {
|
|
|
1983
1750
|
/** Requester id — funder identity fallback for DVMs without descriptor auth. */
|
|
1984
1751
|
requesterId?: string;
|
|
1985
1752
|
/**
|
|
1986
|
-
* Explicit credit fields extracted from the signed body (
|
|
1753
|
+
* Explicit credit fields extracted from the signed body (the signed credit contract). Their
|
|
1987
1754
|
* signature coverage and structural validation happen in `app.ts` before
|
|
1988
1755
|
* payment verification; this layer enforces ownership, the funding
|
|
1989
1756
|
* commitment, and the draw itself.
|
|
1990
1757
|
*/
|
|
1991
1758
|
creditEnvelope?: CreditEnvelope;
|
|
1992
1759
|
/**
|
|
1993
|
-
* Turn this verification into a **fund-only top-up
|
|
1760
|
+
* Turn this verification into a **fund-only top-up**: the rails
|
|
1994
1761
|
* verify and commit exactly as they do for a job, but the ledger leg funds
|
|
1995
1762
|
* without drawing — there is no job to pay for. Set by `POST /v1/credit`;
|
|
1996
1763
|
* `requiredMsats` / `priceFiat` carry the requested top-up amount rather
|
|
@@ -2021,26 +1788,25 @@ interface FundOnlyRequest {
|
|
|
2021
1788
|
/** Client-generated idempotency key for this deposit (`credit_fundings` PK). */
|
|
2022
1789
|
fundId: string;
|
|
2023
1790
|
/**
|
|
2024
|
-
* SHA-256 hex over the raw funding artifact, signed by the caller (
|
|
1791
|
+
* SHA-256 hex over the raw funding artifact, signed by the caller (the signed credit contract
|
|
2025
1792
|
* condition 3). Checked before any rail work: it is what binds the artifact
|
|
2026
1793
|
* presented on the header to the deposit named in the signed body.
|
|
2027
1794
|
*/
|
|
2028
1795
|
commitment: string;
|
|
2029
1796
|
}
|
|
2030
|
-
/** Structured payment validation error detail carried through `PaymentValidationError`. */
|
|
2031
1797
|
interface PaymentErrorDetail {
|
|
2032
1798
|
body: Record<string, unknown>;
|
|
2033
1799
|
status: number;
|
|
2034
1800
|
/**
|
|
2035
1801
|
* mppx challenges to advertise via `WWW-Authenticate` headers on the 402
|
|
2036
|
-
* response
|
|
1802
|
+
* response. Populated whenever the DVM has an mppx handle and
|
|
2037
1803
|
* the request is short on payment — including on payment_invalid, since
|
|
2038
1804
|
* draft-ryan-httpauth-payment-01 requires a fresh challenge on rejection.
|
|
2039
1805
|
*/
|
|
2040
1806
|
mppChallenges?: Challenge.Challenge[];
|
|
2041
1807
|
/**
|
|
2042
1808
|
* x402 `PaymentRequirements` to merge into the 402 body's `accepts`
|
|
2043
|
-
* array under the `x402Version` envelope
|
|
1809
|
+
* array under the `x402Version` envelope. Populated when the
|
|
2044
1810
|
* DVM has x402 wired and dual-serve is on.
|
|
2045
1811
|
*/
|
|
2046
1812
|
x402Requirements?: PaymentRequirements[];
|
|
@@ -2049,30 +1815,30 @@ interface PaymentErrorDetail {
|
|
|
2049
1815
|
/**
|
|
2050
1816
|
* Scheme-level error code for the x402 v2 envelope, kept apart from the
|
|
2051
1817
|
* body's human `message` because a scheme client matches on it verbatim to
|
|
2052
|
-
* decide whether a 402 is correctable
|
|
1818
|
+
* decide whether a 402 is correctable.
|
|
2053
1819
|
*/
|
|
2054
1820
|
x402ErrorCode?: string;
|
|
2055
1821
|
/**
|
|
2056
1822
|
* On a `replay_rejected` from the x402/mpp processed-payment marker
|
|
2057
|
-
*
|
|
2058
|
-
* route feeds it to the
|
|
1823
|
+
* the payment id the original funding committed under. The
|
|
1824
|
+
* route feeds it to the recovery gates (jobs persist it as
|
|
2059
1825
|
* `payment_tx_hash`) so a crash-replayed credential can recover its
|
|
2060
1826
|
* original job instead of a bare 409.
|
|
2061
1827
|
*/
|
|
2062
1828
|
replayPaymentId?: string;
|
|
2063
1829
|
/**
|
|
2064
1830
|
* A short payment that was kept under the fail-closed posture and settled
|
|
2065
|
-
* on the spot
|
|
1831
|
+
* on the spot. The route books it as revenue under a synthetic
|
|
2066
1832
|
* job id — no job exists to carry it — tagged so it stays separable from
|
|
2067
1833
|
* per-job service revenue.
|
|
2068
1834
|
*/
|
|
2069
1835
|
forfeit?: ShortPayForfeit;
|
|
2070
1836
|
/**
|
|
2071
|
-
* Money that landed on a credit even though this request failed
|
|
2072
|
-
*
|
|
1837
|
+
* Money that landed on a credit even though this request failed. Set when a
|
|
1838
|
+
* short mid-job pay funds without completing its ask,
|
|
2073
1839
|
* or an explicit upfront fund-and-draw commits the deposit but the combined
|
|
2074
1840
|
* balance cannot cover the draw. In both cases the caller keeps an available
|
|
2075
|
-
* balance they can reclaim under the
|
|
1841
|
+
* balance they can reclaim under the drain. That is a liability, so
|
|
2076
1842
|
* the deposit has to reach the platform on the 402 path exactly as it does on
|
|
2077
1843
|
* the success path, or the outstanding-liability view is short by precisely
|
|
2078
1844
|
* the money we still owe.
|
|
@@ -2085,7 +1851,7 @@ interface PaymentErrorDetail {
|
|
|
2085
1851
|
*/
|
|
2086
1852
|
funding?: CreditFundingReport;
|
|
2087
1853
|
}
|
|
2088
|
-
/** A kept-but-undelivered short payment, ready to be booked as tagged revenue
|
|
1854
|
+
/** A kept-but-undelivered short payment, ready to be booked as tagged revenue. */
|
|
2089
1855
|
interface ShortPayForfeit {
|
|
2090
1856
|
/** Cashu request id — the rail settlement reference. */
|
|
2091
1857
|
paymentId: string;
|
|
@@ -2101,9 +1867,9 @@ type PaymentResult = PaymentInfo & {
|
|
|
2101
1867
|
error?: PaymentErrorDetail;
|
|
2102
1868
|
/**
|
|
2103
1869
|
* The signed body's `(credit_id, draw_id)` matches an already-recorded
|
|
2104
|
-
* draw (
|
|
1870
|
+
* draw (the draw-idempotency rule — the explicit idempotency regime).
|
|
2105
1871
|
* No rail work ran and no second hold was placed; the route applies the
|
|
2106
|
-
*
|
|
1872
|
+
* proof-of-origin gates and re-issues the original job's response.
|
|
2107
1873
|
*/
|
|
2108
1874
|
replayedDraw?: {
|
|
2109
1875
|
creditId: string;
|
|
@@ -2112,7 +1878,7 @@ type PaymentResult = PaymentInfo & {
|
|
|
2112
1878
|
};
|
|
2113
1879
|
};
|
|
2114
1880
|
/**
|
|
2115
|
-
* Whether a dev-mode server skips payment verification outright
|
|
1881
|
+
* Whether a dev-mode server skips payment verification outright.
|
|
2116
1882
|
*
|
|
2117
1883
|
* Both upfront and mid-job acceptance require verification whenever any Cashu,
|
|
2118
1884
|
* x402, or Tempo rail is configured. An unusable configured rail fails closed;
|
|
@@ -2134,7 +1900,7 @@ declare function devModeSkipsPaymentVerification(opts: {
|
|
|
2134
1900
|
*/
|
|
2135
1901
|
declare const PAYMENT_PROOF_KEYS: readonly ["cashu_token", "x402_payment", "tempo_credential"];
|
|
2136
1902
|
/**
|
|
2137
|
-
* Whether a `payment` message carries a proof on any mid-job rail
|
|
1903
|
+
* Whether a `payment` message carries a proof on any mid-job rail.
|
|
2138
1904
|
*
|
|
2139
1905
|
* Deliberately colocated with the rail dispatch it mirrors, because two call
|
|
2140
1906
|
* sites decide on it: `validateMessageContent` (`server/app.ts`) admits a
|
|
@@ -2146,26 +1912,26 @@ declare const PAYMENT_PROOF_KEYS: readonly ["cashu_token", "x402_payment", "temp
|
|
|
2146
1912
|
declare function hasPaymentProof(content: Record<string, unknown>): boolean;
|
|
2147
1913
|
/**
|
|
2148
1914
|
* Issue one mppx Challenge per registered method for the upfront HTTP-transport
|
|
2149
|
-
* 402 path
|
|
1915
|
+
* 402 path. Mirrors the message-based issuance loop in
|
|
2150
1916
|
* `context.ts` but binds challenges via `meta` (resource path + per-request
|
|
2151
|
-
* nonce) instead of the per-job `pendingMppChallengeIds` binding
|
|
1917
|
+
* nonce) instead of the per-job `pendingMppChallengeIds` binding —
|
|
2152
1918
|
* there's no job at upfront challenge-issue time, so the binding has to live
|
|
2153
1919
|
* inside the challenge itself.
|
|
2154
1920
|
*
|
|
2155
1921
|
* `meta.nonce` is unique-per-issue and HMAC-bound; consumed-id tracking on
|
|
2156
|
-
* the verification side is wired via `ConsumedCredentialStore
|
|
1922
|
+
* the verification side is wired via `ConsumedCredentialStore`, so
|
|
2157
1923
|
* a successfully-accepted credential can't be replayed within its `expires`
|
|
2158
1924
|
* window even if it lands on the same paid endpoint.
|
|
2159
1925
|
*
|
|
2160
|
-
* `btcUsdRate` is resolved once per request by the caller
|
|
1926
|
+
* `btcUsdRate` is resolved once per request by the caller rather
|
|
2161
1927
|
* than fetched here, so every rail this request advertises or verifies prices
|
|
2162
1928
|
* off the same number and a rate-provider outage is handled in one place.
|
|
2163
1929
|
*
|
|
2164
|
-
* `sessionIssuable` is the platform close-observer gate
|
|
1930
|
+
* `sessionIssuable` is the platform close-observer gate, threaded in
|
|
2165
1931
|
* by `DVMServer` and defaulting to issuable for a self-hosted DVM that watches
|
|
2166
1932
|
* its own closes. It used to gate advertisement only, so a `/v1/credit` 402
|
|
2167
1933
|
* kept issuing `tempo/session` challenges the DVM had just stopped advertising
|
|
2168
|
-
*
|
|
1934
|
+
* and its own interface doc said "advertised **and** issued". The
|
|
2169
1935
|
* caller resolves it as "the gate is open, **or** this credit is already bound
|
|
2170
1936
|
* to a channel", so a top-up on an existing channel keeps its challenge while
|
|
2171
1937
|
* the gate is closed; `verifyUpfrontPayment` still refuses any *other* channel
|
|
@@ -2175,11 +1941,9 @@ declare function hasPaymentProof(content: Record<string, unknown>): boolean;
|
|
|
2175
1941
|
* surfaces always keep charge.
|
|
2176
1942
|
*/
|
|
2177
1943
|
declare function issueUpfrontChallenges(mpp: MppxServer, requiredMsats: number, resourcePath: string, btcUsdRate: number, sessionIssuable?: boolean, allowOneShotStablecoin?: unknown, priceFiat?: PriceFiat): Promise<Challenge.Challenge[]>;
|
|
2178
|
-
/** Verify an upfront payment on job submission (Cashu, x402, or MPP). */
|
|
2179
1944
|
declare function verifyUpfrontPayment(opts: UpfrontPaymentOpts): Promise<PaymentResult>;
|
|
2180
1945
|
/**
|
|
2181
1946
|
* A management credential signed for a channel other than the one required
|
|
2182
|
-
* (internal-review).
|
|
2183
1947
|
*
|
|
2184
1948
|
* Typed rather than a bare `Error` because the drain route has to tell it apart
|
|
2185
1949
|
* from the transport and chain failures its catch ladder reconciles: this one is
|
|
@@ -2206,7 +1970,7 @@ declare function verifyTempoSessionManagementCredential(args: {
|
|
|
2206
1970
|
*
|
|
2207
1971
|
* The drain route re-asserts its close intent here rather than a line above
|
|
2208
1972
|
* the call so that a credential signed for another channel is refused before
|
|
2209
|
-
* the reservation is touched
|
|
1973
|
+
* the reservation is touched. Only this function knows where that
|
|
2210
1974
|
* seam is, so putting the hook here makes the ordering structural instead of a
|
|
2211
1975
|
* comment the next edit can reorder past. A throw propagates to the caller
|
|
2212
1976
|
* with nothing broadcast.
|
|
@@ -2218,7 +1982,7 @@ declare function verifyTempoSessionManagementCredential(args: {
|
|
|
2218
1982
|
cumulativeAmount?: string;
|
|
2219
1983
|
}>;
|
|
2220
1984
|
/**
|
|
2221
|
-
* Every machine code a flat payment-error body can carry
|
|
1985
|
+
* Every machine code a flat payment-error body can carry — the six
|
|
2222
1986
|
* codes constructed directly in this file plus the agent-wallet cashu rejection
|
|
2223
1987
|
* codes ({@link AccumulatorRejectionCode}), which surface verbatim through the
|
|
2224
1988
|
* accumulator branch of {@link verifyAccumulatorReceipt}. `fx_rate_unavailable`
|
|
@@ -2228,25 +1992,9 @@ declare function verifyTempoSessionManagementCredential(args: {
|
|
|
2228
1992
|
*/
|
|
2229
1993
|
type PaymentErrorCode = "payment_invalid" | "x402_not_offered" | "x402_settlement_ambiguous" | "cashu_unavailable" | "cashu_not_p2pk_locked" | "mint_health_pending" | "cashu_mint_sick" | "payment_insufficient" | "payment_unapplied" | "insufficient_credit" | "credit_expired" | "credit_unbacked" | "credit_not_found" | "tempo_channel_closing" | "settlement_pending" | "draw_conflict" | "funding_commitment_mismatch" | "credit_not_supported" | "one_shot_credit_not_supported" | "credit_below_min" | "below_rail_minimum" | "credit_over_max" | "credit_id_unavailable" | "nothing_to_drain" | "drain_below_dust" | "drain_not_found" | "drain_conflict" | "drain_method_unsupported" | "tempo_channel_exit_required" | "x402_settlement_submission_failed" | X402SettlementReconciliationReason | "lightning_settlement_pending" | "lightning_invoice_expired" | "lightning_unavailable" | AccumulatorRejectionCode;
|
|
2230
1994
|
/**
|
|
2231
|
-
*
|
|
2232
|
-
*
|
|
2233
|
-
*
|
|
2234
|
-
* human and branches retries on. `display`, `hint`, and `retryable` default from
|
|
2235
|
-
* {@link PAYMENT_ERROR_COPY} keyed on `code`; `message` is the per-site operator
|
|
2236
|
-
* log line. The prose fields can be overridden for dynamic cases (e.g.
|
|
2237
|
-
* `cashu_mint_sick` naming the currently-healthy mints, or a retained deposit
|
|
2238
|
-
* distinguishing "not spent" from "not paid"). `extra` carries a code's ad-hoc
|
|
2239
|
-
* fields (`mints`, `required_msats`, `received_msats`) and is merged last so
|
|
2240
|
-
* those are preserved.
|
|
2241
|
-
*
|
|
2242
|
-
* `retryable` overrides on the same terms, and for the same reason: a code that
|
|
2243
|
-
* covers more than one refusal can be terminal for one of them and clear on its
|
|
2244
|
-
* own for another — `drain_conflict` is both (internal-review). Override it only
|
|
2245
|
-
* alongside the `hint`, since the two are one answer: a hint that says retry
|
|
2246
|
-
* under `retryable: false` is the disagreement this exists to prevent.
|
|
2247
|
-
*
|
|
2248
|
-
* Upstream of {@link buildPaymentErrorResponse}, which serializes the body onto
|
|
2249
|
-
* a `Response` — this shapes it.
|
|
1995
|
+
* Per-code display, hint and retryability may be overridden for a particular
|
|
1996
|
+
* refusal. Keep the hint and retry decision consistent. Extra fields merge
|
|
1997
|
+
* last, so callers can supply code-specific amounts and alternatives.
|
|
2250
1998
|
*/
|
|
2251
1999
|
declare function paymentErrorBody(code: PaymentErrorCode, opts: {
|
|
2252
2000
|
message: string;
|
|
@@ -2258,14 +2006,14 @@ declare function paymentErrorBody(code: PaymentErrorCode, opts: {
|
|
|
2258
2006
|
/**
|
|
2259
2007
|
* Build a payment-error response carrying a JSON error body plus one
|
|
2260
2008
|
* `WWW-Authenticate: Payment <serialized challenge>` header per mppx challenge
|
|
2261
|
-
* (
|
|
2009
|
+
* (per draft-ryan-httpauth-payment-01). When `challenges` is empty,
|
|
2262
2010
|
* returns a plain JSON response (Cashu/x402-only DVMs).
|
|
2263
2011
|
*
|
|
2264
2012
|
* Status defaults to 402 (Payment Required) but callers can override — e.g.
|
|
2265
|
-
* the
|
|
2013
|
+
* the sick-mint short-circuit returns 503 because the failure is a
|
|
2266
2014
|
* mint-side outage, not a payment problem.
|
|
2267
2015
|
*
|
|
2268
|
-
* When `x402Requirements` is present (
|
|
2016
|
+
* When `x402Requirements` is present (x402 dual-serve), merge the
|
|
2269
2017
|
* x402-spec envelope `{ x402Version, accepts: [...] }` into the body next to
|
|
2270
2018
|
* the existing dvmkit error fields so x402 clients can find their retry
|
|
2271
2019
|
* shape on the same response. The MPP `WWW-Authenticate` header and the
|
|
@@ -2275,7 +2023,6 @@ declare function paymentErrorBody(code: PaymentErrorCode, opts: {
|
|
|
2275
2023
|
* are single-use and shared caches must not return them to other clients.
|
|
2276
2024
|
*/
|
|
2277
2025
|
declare function buildPaymentErrorResponse(body: Record<string, unknown>, challenges?: Challenge.Challenge[], x402Requirements?: PaymentRequirements[], status?: number, x402V2Requirements?: PaymentRequirementsV2[], x402V2Resource?: string, x402ExactVersions?: X402ExactVersionSupport, x402ErrorCode?: string): Response;
|
|
2278
|
-
/** Options for processing an incoming mid-job payment message. */
|
|
2279
2026
|
interface IncomingPaymentOpts {
|
|
2280
2027
|
mints?: string[];
|
|
2281
2028
|
x402Config?: X402Config;
|
|
@@ -2288,7 +2035,7 @@ interface IncomingPaymentOpts {
|
|
|
2288
2035
|
mpp?: MppxServer;
|
|
2289
2036
|
/** Resource URL bound into the rebuilt x402 `PaymentRequirements`. */
|
|
2290
2037
|
resourcePath?: string;
|
|
2291
|
-
/** Cashu receive mode
|
|
2038
|
+
/** Cashu receive mode. Mirrors {@link UpfrontPaymentOpts.cashuMode}. */
|
|
2292
2039
|
cashuMode?: CashuMode;
|
|
2293
2040
|
/** Per-DVM Postgres pool — required for the `p2pk-accumulator` path. */
|
|
2294
2041
|
accumulatorDb?: AccumulatorPool;
|
|
@@ -2297,28 +2044,28 @@ interface IncomingPaymentOpts {
|
|
|
2297
2044
|
dvmId?: string;
|
|
2298
2045
|
/** Active NUT-11 P2PK lock pubkeys (current + retired-in-grace). */
|
|
2299
2046
|
lockPubkeys?: LockPubkey[];
|
|
2300
|
-
/** Runtime mint-health tracker
|
|
2047
|
+
/** Runtime mint-health tracker. */
|
|
2301
2048
|
mintHealthTracker?: MintHealthTracker;
|
|
2302
2049
|
/**
|
|
2303
|
-
* Host fx fetcher for the non-sat rails
|
|
2050
|
+
* Host fx fetcher for the non-sat rails. See
|
|
2304
2051
|
* {@link UpfrontPaymentOpts.fxFetcher}.
|
|
2305
2052
|
*/
|
|
2306
2053
|
fxFetcher?: FxFetcher;
|
|
2307
2054
|
/**
|
|
2308
|
-
* Credit ledger the mid-job top-up funds and grows (
|
|
2055
|
+
* Credit ledger the mid-job top-up funds and grows (the credit lifecycle contract:
|
|
2309
2056
|
* "mid-job `requestPayment` becomes a top-up"). Absent ⇒ the payment is
|
|
2310
2057
|
* accepted with pre-credits semantics, exactly as before.
|
|
2311
2058
|
*/
|
|
2312
2059
|
creditLedger?: CreditLedgerLike;
|
|
2313
2060
|
/**
|
|
2314
|
-
* Durable processed-payment markers
|
|
2061
|
+
* Durable processed-payment markers. x402/mpp money moves outside
|
|
2315
2062
|
* dvmkit, so the marker + `fund` + `growDraw` in one transaction is what
|
|
2316
2063
|
* makes the mid-job ledger leg atomic and crash-replay safe — the cashu path
|
|
2317
2064
|
* gets the same property from the accumulator's own commit transaction.
|
|
2318
2065
|
*/
|
|
2319
2066
|
processedPayments?: ProcessedPaymentStore;
|
|
2320
2067
|
/**
|
|
2321
|
-
* Reporter-owned transactional outbox enqueue
|
|
2068
|
+
* Reporter-owned transactional outbox enqueue. Runs inside the
|
|
2322
2069
|
* mid-job rail transaction after the ledger top-up/growth and before commit.
|
|
2323
2070
|
*/
|
|
2324
2071
|
enqueueCreditDeposit?: CreditDepositEnqueue;
|
|
@@ -2328,20 +2075,15 @@ interface IncomingPaymentOpts {
|
|
|
2328
2075
|
creditTtlMs?: number;
|
|
2329
2076
|
}
|
|
2330
2077
|
/**
|
|
2331
|
-
*
|
|
2332
|
-
*
|
|
2333
|
-
*
|
|
2334
|
-
* inside Tx C so verify+credit land atomically.
|
|
2335
|
-
*
|
|
2336
|
-
* `snapshot` carries the read-only job state the verification path needs
|
|
2337
|
-
* (`pendingPaymentMsats` for amount checks, the MPP/x402 per-job bindings,
|
|
2338
|
-
* `paymentRail` for the same-rail accumulator check).
|
|
2078
|
+
* Job state read by incoming-payment verification; the job itself is not mutated. Rail
|
|
2079
|
+
* acceptance and ledger funding can still occur before the caller applies the returned credit in
|
|
2080
|
+
* Tx C.
|
|
2339
2081
|
*/
|
|
2340
2082
|
interface VerifyIncomingSnapshot {
|
|
2341
2083
|
pendingPaymentMsats?: number;
|
|
2342
2084
|
pendingMppChallengeIds?: string[];
|
|
2343
2085
|
/**
|
|
2344
|
-
* Per-job x402 binding
|
|
2086
|
+
* Per-job x402 binding. When set, the incoming `x402_payment`'s
|
|
2345
2087
|
* decoded `authorization.nonce` must match this value or the credential is
|
|
2346
2088
|
* rejected — closes the pre-settlement cross-job replay window. Pre-fix
|
|
2347
2089
|
* DVMs leave this `undefined`; the verify-side check no-ops for them.
|
|
@@ -2350,9 +2092,9 @@ interface VerifyIncomingSnapshot {
|
|
|
2350
2092
|
/** Exact advertised USDC microunits paired with `pendingX402Nonce`. */
|
|
2351
2093
|
pendingX402AmountUsdcMicro?: string;
|
|
2352
2094
|
paymentRail?: FundingMethod;
|
|
2353
|
-
/** The credit the upfront leg funded and drew, when it had one
|
|
2095
|
+
/** The credit the upfront leg funded and drew, when it had one. */
|
|
2354
2096
|
creditId?: string;
|
|
2355
|
-
/** The draw this job holds on {@link creditId}; the top-up grows it
|
|
2097
|
+
/** The draw this job holds on {@link creditId}; the top-up grows it. */
|
|
2356
2098
|
drawId?: string;
|
|
2357
2099
|
/**
|
|
2358
2100
|
* The job's current settlement reference. Used to tell an explicit
|
|
@@ -2361,17 +2103,17 @@ interface VerifyIncomingSnapshot {
|
|
|
2361
2103
|
* upfront leg established.
|
|
2362
2104
|
*/
|
|
2363
2105
|
paymentTxHash?: string;
|
|
2364
|
-
/** Fiat envelope pinned when the ask was emitted
|
|
2106
|
+
/** Fiat envelope pinned when the ask was emitted. See `JobRecord`. */
|
|
2365
2107
|
pendingPaymentFiatMicro?: number;
|
|
2366
2108
|
pendingPaymentFiatCurrency?: string;
|
|
2367
2109
|
/**
|
|
2368
2110
|
* Everything this job has asked for mid-job, summed and never decremented
|
|
2369
|
-
*
|
|
2111
|
+
* the ceiling on its draw growth. See `JobRecord`, including
|
|
2370
2112
|
* why it can't be derived from the outstanding figure above.
|
|
2371
2113
|
*/
|
|
2372
2114
|
askedTopUpMicro?: number;
|
|
2373
2115
|
askedTopUpCurrency?: string;
|
|
2374
|
-
/** Why the total above can't express a ceiling, when it can't
|
|
2116
|
+
/** Why the total above can't express a ceiling, when it can't. */
|
|
2375
2117
|
topUpCapUnenforcedReason?: TopUpCapUnenforcedReason;
|
|
2376
2118
|
/**
|
|
2377
2119
|
* Caller identity for a top-up that has to mint a *fresh* implicit credit
|
|
@@ -2382,7 +2124,6 @@ interface VerifyIncomingSnapshot {
|
|
|
2382
2124
|
requesterPubkey?: string;
|
|
2383
2125
|
requesterId?: string;
|
|
2384
2126
|
}
|
|
2385
|
-
/** Result of `verifyIncomingPayment`. */
|
|
2386
2127
|
interface VerifiedIncomingPayment {
|
|
2387
2128
|
paidMsats: number;
|
|
2388
2129
|
paymentMint?: string;
|
|
@@ -2391,21 +2132,21 @@ interface VerifiedIncomingPayment {
|
|
|
2391
2132
|
nativeAmount?: number;
|
|
2392
2133
|
nativeAsset?: "sats" | "usdc" | "usdc.e" | "usd-cents";
|
|
2393
2134
|
cashuFlow?: "p2pk_accumulator";
|
|
2394
|
-
/** True when this credit should accumulate onto an existing same-rail native amount
|
|
2135
|
+
/** True when this credit should accumulate onto an existing same-rail native amount. */
|
|
2395
2136
|
sameRail: boolean;
|
|
2396
2137
|
/**
|
|
2397
|
-
* Credit this top-up opened
|
|
2138
|
+
* Credit this top-up opened. Only set when the job had none — a
|
|
2398
2139
|
* free-then-paid job — so Tx C can bind it; a job that already holds a credit
|
|
2399
2140
|
* keeps its binding, because the top-up grew that draw.
|
|
2400
2141
|
*/
|
|
2401
2142
|
creditId?: string;
|
|
2402
2143
|
/** The draw opened alongside {@link creditId}. */
|
|
2403
2144
|
drawId?: string;
|
|
2404
|
-
/** The funding leg, reported to the platform as a deposit
|
|
2145
|
+
/** The funding leg, reported to the platform as a deposit. */
|
|
2405
2146
|
funding?: CreditFundingReport;
|
|
2406
2147
|
/**
|
|
2407
2148
|
* Fiat micro this payment funded that the job's draw did not take
|
|
2408
|
-
*
|
|
2149
|
+
* See `PaymentInfo.unappliedMicro`. When it equals the whole
|
|
2409
2150
|
* funding, the payment moved no job counters at all and Tx C is handed a
|
|
2410
2151
|
* `null` delta.
|
|
2411
2152
|
*/
|
|
@@ -2413,7 +2154,7 @@ interface VerifiedIncomingPayment {
|
|
|
2413
2154
|
}
|
|
2414
2155
|
/**
|
|
2415
2156
|
* Verify an incoming payment without mutating the job. Returns the credit
|
|
2416
|
-
* shape on success or an error envelope on failure.
|
|
2157
|
+
* shape on success or an error envelope on failure.
|
|
2417
2158
|
*
|
|
2418
2159
|
* The rails dispatched on below are the list {@link PAYMENT_PROOF_KEYS}
|
|
2419
2160
|
* carries — add one here and it must be added there in the same commit.
|
|
@@ -2428,27 +2169,19 @@ declare function verifyIncomingPayment(body: {
|
|
|
2428
2169
|
status: number;
|
|
2429
2170
|
/**
|
|
2430
2171
|
* A short mid-job pay that funded a credit before this request failed
|
|
2431
|
-
*
|
|
2172
|
+
* still a deposit, still reported. See `PaymentErrorDetail`.
|
|
2432
2173
|
*/
|
|
2433
2174
|
funding?: CreditFundingReport;
|
|
2434
2175
|
};
|
|
2435
2176
|
}>;
|
|
2436
2177
|
/**
|
|
2437
2178
|
* Apply a verified payment result to the in-memory job. Used by callers that
|
|
2438
|
-
* don't run Tx C (e.g., the legacy isolate-job-manager path).
|
|
2179
|
+
* don't run Tx C (e.g., the legacy isolate-job-manager path).
|
|
2439
2180
|
*/
|
|
2440
2181
|
declare function applyPaymentInfoToJob(job: ServerJob, info: VerifiedIncomingPayment): void;
|
|
2441
2182
|
/**
|
|
2442
|
-
*
|
|
2443
|
-
*
|
|
2444
|
-
* **The isolate-runtime path, and deliberately ledger-less** (spec §3). Its
|
|
2445
|
-
* only caller is `platform/isolate-job-manager.ts`; isolate DVMs have no
|
|
2446
|
-
* Postgres of their own, so they run implicit N=1 with today's semantics
|
|
2447
|
-
* preserved and never advertise credit. The credits-carrying mid-job path is
|
|
2448
|
-
* {@link verifyIncomingPayment} + `JobManager.processPayment` (Tx C), where
|
|
2449
|
-
* internal-review wired `fund` + `growDraw`. Wiring a ledger here would need a
|
|
2450
|
-
* platform-side ledger leg with its own funding-atomicity and receipt-key
|
|
2451
|
-
* story — the trust-surface expansion internal-review exists to decide.
|
|
2183
|
+
* Legacy ledgerless payment path. Mutates the job on acceptance; durable credit hosts use
|
|
2184
|
+
* verifyIncomingPayment and apply its result in Tx C.
|
|
2452
2185
|
*/
|
|
2453
2186
|
declare function processIncomingPayment(job: ServerJob, body: {
|
|
2454
2187
|
type: MessageType;
|
|
@@ -2463,22 +2196,22 @@ interface CommittedFunding {
|
|
|
2463
2196
|
/** Snapshot fixed immediately after this request's funding leg, before any draw. */
|
|
2464
2197
|
fundingCredit?: CreditSnapshot;
|
|
2465
2198
|
/**
|
|
2466
|
-
* Absent for a `/v1/credit` fund-only top-up
|
|
2199
|
+
* Absent for a `/v1/credit` fund-only top-up — money landed on the
|
|
2467
2200
|
* credit with no job to pay for — for a short mid-job pay, which funds
|
|
2468
|
-
* without drawing or growing
|
|
2469
|
-
* whose combined balance cannot cover the draw
|
|
2201
|
+
* without drawing or growing, and for an explicit short deposit
|
|
2202
|
+
* whose combined balance cannot cover the draw. On a mid-job
|
|
2470
2203
|
* top-up this is the **grown** draw, so `amountMicro` is the job's running
|
|
2471
2204
|
* total, not this leg's contribution.
|
|
2472
2205
|
*/
|
|
2473
2206
|
draw?: DrawResult;
|
|
2474
2207
|
/**
|
|
2475
2208
|
* An explicit short deposit committed, but the combined available balance
|
|
2476
|
-
* could not cover the full-price draw
|
|
2209
|
+
* could not cover the full-price draw. Preserved instead of
|
|
2477
2210
|
* thrown so the rail receive and deposit commit atomically with no draw.
|
|
2478
2211
|
*/
|
|
2479
2212
|
drawRefusal?: CreditLedgerError;
|
|
2480
2213
|
/**
|
|
2481
|
-
* What this leg added to an already-placed draw
|
|
2214
|
+
* What this leg added to an already-placed draw. Set only on a
|
|
2482
2215
|
* mid-job top-up; the upfront legs contribute the whole draw, so they leave
|
|
2483
2216
|
* it unset and `draw.railValue` is the same thing.
|
|
2484
2217
|
*/
|
|
@@ -2488,7 +2221,7 @@ interface CommittedFunding {
|
|
|
2488
2221
|
drawNative: number | null;
|
|
2489
2222
|
};
|
|
2490
2223
|
/**
|
|
2491
|
-
* Fiat micro that landed on the credit but grew no draw
|
|
2224
|
+
* Fiat micro that landed on the credit but grew no draw — a
|
|
2492
2225
|
* duplicate payment for an ask already satisfied, the clipped tail of an
|
|
2493
2226
|
* oversized one, or a payment that arrived after the ask was met. It stays
|
|
2494
2227
|
* the caller's available balance; `UnappliedCredit` on the 201 is how they
|
|
@@ -2503,9 +2236,9 @@ interface CommittedFunding {
|
|
|
2503
2236
|
*
|
|
2504
2237
|
* Exported because the builder-supplied `onCreditFunded` override is a second
|
|
2505
2238
|
* door onto the same report, and both must say the same thing: hand-rolling the
|
|
2506
|
-
* mapping there once dropped the
|
|
2239
|
+
* mapping there once dropped the `tempoChannel` block, so a DVM
|
|
2507
2240
|
* reporting through the override registered no channel with the close observer
|
|
2508
|
-
*
|
|
2241
|
+
* One builder, no drift.
|
|
2509
2242
|
*/
|
|
2510
2243
|
declare function creditDepositPayload(dvmId: string, funding: CreditFundingReport): CreditDepositPayload;
|
|
2511
2244
|
/** Finish the local ledger effect retained by a durable exact-settlement intent. */
|
|
@@ -2518,7 +2251,6 @@ declare function repairX402ExactSettlementEffect(args: {
|
|
|
2518
2251
|
tx?: CreditLedgerQuerier;
|
|
2519
2252
|
}): Promise<CommittedFunding | undefined>;
|
|
2520
2253
|
|
|
2521
|
-
/** Everything the issuer needs to sign for one DVM. */
|
|
2522
2254
|
interface ReceiptIssuerOpts {
|
|
2523
2255
|
/** 32-byte hex receipt secret — `DVMKIT_RECEIPT_KEY`, or an ephemeral dev key. */
|
|
2524
2256
|
secret: string;
|
|
@@ -2528,14 +2260,8 @@ interface ReceiptIssuerOpts {
|
|
|
2528
2260
|
dvmId: string;
|
|
2529
2261
|
}
|
|
2530
2262
|
/**
|
|
2531
|
-
*
|
|
2532
|
-
*
|
|
2533
|
-
* Constructed only when a receipt key is wired, so the presence of an issuer
|
|
2534
|
-
* *is* the "this DVM emits receipts" signal that `/v1/info` advertises. Pure:
|
|
2535
|
-
* it holds the key and the slug and turns a terminal `JobRecord` plus an
|
|
2536
|
-
* allocated sequence number into signed bytes — sequence allocation and
|
|
2537
|
-
* persistence belong to the store, which is what makes issuance idempotent
|
|
2538
|
-
* across machines.
|
|
2263
|
+
* An issuer indicates receipt support. Sequence allocation and write-once
|
|
2264
|
+
* persistence belong to the store; signing alone does not provide idempotency.
|
|
2539
2265
|
*/
|
|
2540
2266
|
declare class ReceiptIssuer {
|
|
2541
2267
|
/** x-only pubkey this issuer signs under; the attested `receipt_pubkey`. */
|
|
@@ -2545,20 +2271,14 @@ declare class ReceiptIssuer {
|
|
|
2545
2271
|
private readonly dvmId;
|
|
2546
2272
|
constructor(opts: ReceiptIssuerOpts);
|
|
2547
2273
|
/**
|
|
2548
|
-
*
|
|
2549
|
-
*
|
|
2550
|
-
*
|
|
2551
|
-
*
|
|
2552
|
-
* sequence number and still attest an outcome. Whether feedback is
|
|
2553
|
-
* paid-only is the feedback layer's call, not this one's.
|
|
2554
|
-
*
|
|
2555
|
-
* `credit` is the resolved draw block (internal-review) — present on every job
|
|
2556
|
-
* whose payment funded/drew the ledger, absent otherwise. Signature-
|
|
2557
|
-
* compatible either way: `canonicalize` omits absent keys.
|
|
2274
|
+
* seq is store-allocated; issuedAt is epoch seconds. Free jobs also consume a
|
|
2275
|
+
* sequence and attest their outcome. Timing/attribution comes from the committed
|
|
2276
|
+
* terminal row; legacy rows omit unknown historical fields. credit is present
|
|
2277
|
+
* only for a resolved ledger draw.
|
|
2558
2278
|
*/
|
|
2559
2279
|
issue(record: JobRecord, seq: number, issuedAt?: number, credit?: ReceiptCredit): JobReceipt;
|
|
2560
2280
|
/**
|
|
2561
|
-
* Countersign one reclaim event
|
|
2281
|
+
* Countersign one reclaim event. Unlike job receipts there is
|
|
2562
2282
|
* no store-allocated sequence — the drain's own `ledger_seq` (taken under
|
|
2563
2283
|
* the credit row lock, shared with draws) already orders it in the
|
|
2564
2284
|
* per-credit evidence chain.
|
|
@@ -2650,38 +2370,31 @@ declare class X402FacilitatorHealth implements X402FacilitatorHealthLike {
|
|
|
2650
2370
|
private refreshOnce;
|
|
2651
2371
|
}
|
|
2652
2372
|
|
|
2653
|
-
/** Options for creating a JobManager. */
|
|
2654
2373
|
interface JobManagerOpts {
|
|
2655
|
-
/** Environment variables for ctx.env. */
|
|
2656
2374
|
env: Record<string, string>;
|
|
2657
|
-
/** KV store for ctx.store. */
|
|
2658
2375
|
store: KVStore;
|
|
2659
|
-
/** Cashu mints accepted for payment. */
|
|
2660
2376
|
mints?: string[];
|
|
2661
|
-
/**
|
|
2377
|
+
/** Durable in production; defaults to memory only in development/test mode. */
|
|
2662
2378
|
jobStore?: JobStore;
|
|
2663
|
-
/**
|
|
2379
|
+
/** Skips payment only when no payment rail is configured. Wired rails still verify. */
|
|
2664
2380
|
devMode?: boolean;
|
|
2665
2381
|
/** Payment methods this DVM accepts. Default: derived by the server from configured rails. */
|
|
2666
2382
|
paymentMethods?: PaymentMethod[];
|
|
2667
|
-
/** x402 stablecoin payment configuration. */
|
|
2668
2383
|
x402?: X402Config;
|
|
2669
2384
|
/** Durable exact-settlement coordinator shared with upfront verification. */
|
|
2670
2385
|
x402ExactSettlement?: X402ExactSettlementServer;
|
|
2671
2386
|
/** Live facilitator capabilities for version-specific mid-job payment handling. */
|
|
2672
2387
|
x402FacilitatorHealth?: X402FacilitatorHealthLike;
|
|
2673
|
-
/** MPP multi-rail payment handle (built via `createMppFromOpts`). */
|
|
2674
2388
|
mpp?: MppxServer;
|
|
2675
|
-
/** Cashu receive mode (internal-review). */
|
|
2676
2389
|
cashuMode?: CashuMode;
|
|
2677
|
-
/** Builder's NUT-11 P2PK lock pubkey
|
|
2390
|
+
/** Builder's NUT-11 P2PK lock pubkey. Required for accumulator mode. */
|
|
2678
2391
|
lockPubkey?: string;
|
|
2679
2392
|
/** Test-only atomic Cashu backend, supplied by the development host. */
|
|
2680
2393
|
disposableCashu?: DisposableCashuCommit;
|
|
2681
2394
|
/** Postgres pool for wallet accumulator persistence. */
|
|
2682
2395
|
db?: Pool;
|
|
2683
2396
|
/**
|
|
2684
|
-
* Agent-wallet
|
|
2397
|
+
* Agent-wallet: per-DVM identifier used for the scheduler advisory
|
|
2685
2398
|
* lock and the `(dvm_id, request_id)` replay key. Defaults to `DVMKIT_DVM_ID`
|
|
2686
2399
|
* env at boot in `createDVMHost()`.
|
|
2687
2400
|
*/
|
|
@@ -2690,18 +2403,18 @@ interface JobManagerOpts {
|
|
|
2690
2403
|
authAudience?: SignedRequestAudience;
|
|
2691
2404
|
/**
|
|
2692
2405
|
* Shared fx fetcher used to convert USD-string descriptor prices and
|
|
2693
|
-
* fiat-form `requestPayment` calls into sats
|
|
2406
|
+
* fiat-form `requestPayment` calls into sats. The SDK server owns
|
|
2694
2407
|
* one per process so concurrent quotes share the 60s cache window.
|
|
2695
2408
|
*/
|
|
2696
2409
|
fxFetcher: FxFetcher;
|
|
2697
2410
|
/**
|
|
2698
|
-
* Signs a receipt at every terminal transition
|
|
2411
|
+
* Signs a receipt at every terminal transition. Absent when the
|
|
2699
2412
|
* DVM has no receipt key wired — jobs then terminate exactly as before and
|
|
2700
2413
|
* `/v1/info` doesn't advertise `receipts`.
|
|
2701
2414
|
*/
|
|
2702
2415
|
receiptIssuer?: ReceiptIssuer;
|
|
2703
2416
|
/**
|
|
2704
|
-
* Credit ledger for the terminal funnel
|
|
2417
|
+
* Credit ledger for the terminal funnel: a job carrying a
|
|
2705
2418
|
* `creditId`/`drawId` settles its draw on success and releases it on
|
|
2706
2419
|
* failure/cancel — this is how "no debit on job failure" is mechanically
|
|
2707
2420
|
* real. The resolution runs before receipt issuance so the receipt's
|
|
@@ -2709,31 +2422,31 @@ interface JobManagerOpts {
|
|
|
2709
2422
|
*/
|
|
2710
2423
|
creditLedger?: CreditLedgerLike;
|
|
2711
2424
|
/**
|
|
2712
|
-
* Durable processed-payment markers
|
|
2713
|
-
* mid-job top-up path
|
|
2425
|
+
* Durable processed-payment markers, threaded through to the
|
|
2426
|
+
* mid-job top-up path so an x402/mpp payment's marker, `fund`, and
|
|
2714
2427
|
* `growDraw` commit together.
|
|
2715
2428
|
*/
|
|
2716
2429
|
processedPayments?: ProcessedPaymentStore;
|
|
2717
2430
|
/**
|
|
2718
2431
|
* Reporter-owned transactional deposit enqueue threaded into mid-job rail
|
|
2719
|
-
* commits
|
|
2432
|
+
* commits.
|
|
2720
2433
|
*/
|
|
2721
2434
|
enqueueCreditDeposit?: CreditDepositEnqueue;
|
|
2722
|
-
/** Reporter outbox inserted atomically with a terminal draw release
|
|
2435
|
+
/** Reporter outbox inserted atomically with a terminal draw release. */
|
|
2723
2436
|
enqueueCreditDrawRelease?: CreditDrawReleaseEnqueue;
|
|
2724
2437
|
/** Credit lifetime a top-up mints with, from the DVM's advertised `credit.ttl`. */
|
|
2725
2438
|
creditTtlMs?: number;
|
|
2726
2439
|
/**
|
|
2727
|
-
* The DVM's declared pricing currency
|
|
2440
|
+
* The DVM's declared pricing currency, which is the currency of
|
|
2728
2441
|
* every credit it opens. Threaded into the job context so a mid-job ask on a
|
|
2729
2442
|
* job that holds no credit yet is still pinned in the denomination its first
|
|
2730
|
-
* payment will open the credit in
|
|
2443
|
+
* payment will open the credit in. `DVMServer` passes its resolved
|
|
2731
2444
|
* `pricingCurrency`; a host building a `JobManager` directly falls back to the
|
|
2732
2445
|
* descriptor, and then to `"usd"`.
|
|
2733
2446
|
*/
|
|
2734
2447
|
pricingCurrency?: string;
|
|
2735
2448
|
/**
|
|
2736
|
-
* Callback fired when a mid-job top-up moves money onto a credit
|
|
2449
|
+
* Callback fired when a mid-job top-up moves money onto a credit.
|
|
2737
2450
|
* The platform books it as a **deposit** — a liability until drawn, never
|
|
2738
2451
|
* summed into revenue — so a top-up that skipped this would leave the
|
|
2739
2452
|
* outstanding-liability view short. Mirrors `DVMServer.reportCreditDeposit`,
|
|
@@ -2741,7 +2454,7 @@ interface JobManagerOpts {
|
|
|
2741
2454
|
*/
|
|
2742
2455
|
onCreditFunded?: (info: CreditDepositPayload) => void | Promise<void>;
|
|
2743
2456
|
/**
|
|
2744
|
-
* Callback fired when a paid job completes
|
|
2457
|
+
* Callback fired when a paid job completes. Container-runtime DVMs
|
|
2745
2458
|
* use this to report revenue to the platform via `RevenueReporter`. Mirrors
|
|
2746
2459
|
* the isolate path's `IsolateJobManager.opts.onJobCompleted`.
|
|
2747
2460
|
*/
|
|
@@ -2773,16 +2486,18 @@ interface JobManagerOpts {
|
|
|
2773
2486
|
}) => void | Promise<void>;
|
|
2774
2487
|
/**
|
|
2775
2488
|
* Callback fired when a terminal job declared a builder cost but emitted no
|
|
2776
|
-
* revenue report
|
|
2489
|
+
* revenue report. Container-runtime DVMs use the reporter's
|
|
2777
2490
|
* durable `job-cost` outbox path. The receiver de-duplicates by DVM + job,
|
|
2778
2491
|
* independently of the rail-keyed revenue ledger.
|
|
2779
2492
|
*/
|
|
2780
2493
|
onJobCost?: (info: JobCostReportPayload) => void | Promise<void>;
|
|
2494
|
+
/** SDK-owned hosted terminal outbox callback. */
|
|
2495
|
+
onJobTerminal?: (info: JobTerminalReportPayload) => Promise<void>;
|
|
2781
2496
|
/**
|
|
2782
2497
|
* Callback fired when the stale-job reaper force-fails a *paid* job — its
|
|
2783
2498
|
* pending credit draw is released rather than settled, because only a
|
|
2784
|
-
* completed job settles a draw
|
|
2785
|
-
* to a paid-job-death report → operator `notice
|
|
2499
|
+
* completed job settles a draw. Container-runtime DVMs wire this
|
|
2500
|
+
* to a paid-job-death report → operator `notice`. Never fires for
|
|
2786
2501
|
* free jobs (`paidMsats === 0`). Fired fire-and-forget so a throw or a slow
|
|
2787
2502
|
* report can't wedge the sweep.
|
|
2788
2503
|
*/
|
|
@@ -2799,23 +2514,17 @@ interface JobManagerOpts {
|
|
|
2799
2514
|
}) => void | Promise<void>;
|
|
2800
2515
|
/**
|
|
2801
2516
|
* Callback fired when a settled credit draw cannot book a revenue event
|
|
2802
|
-
* because no usable payment rail is available
|
|
2517
|
+
* because no usable payment rail is available. Container-runtime
|
|
2803
2518
|
* DVMs auto-wire this to the platform's deduped operator notice. Fired
|
|
2804
2519
|
* fire-and-forget so alert delivery cannot block terminal handling or draw
|
|
2805
2520
|
* reconciliation.
|
|
2806
2521
|
*/
|
|
2807
2522
|
onRevenueSkippedNoRail?: (info: RevenueSkippedNoRailPayload) => void | Promise<void>;
|
|
2808
2523
|
/**
|
|
2809
|
-
*
|
|
2810
|
-
*
|
|
2811
|
-
*
|
|
2812
|
-
*
|
|
2813
|
-
* a worker crash, OOM, or silent `messageAppender` failure. Default:
|
|
2814
|
-
* `2 × descriptor.idleTimeout + STALE_JOB_WATCHDOG_HEADROOM_MS`, matching
|
|
2815
|
-
* the internal-review acceptance criterion against scrape's `JOB_TIMEOUT_MS`.
|
|
2816
|
-
* Builders whose per-handler wall-clock bound exceeds 90s must override
|
|
2817
|
-
* this — otherwise the watchdog fires on a legitimate in-flight handler.
|
|
2818
|
-
* Set to `0` to disable (test seam).
|
|
2524
|
+
* Awaiting-input age in milliseconds before cancellation. Defaults to
|
|
2525
|
+
* twice descriptor.idleTimeout (seconds), converted to milliseconds, plus
|
|
2526
|
+
* STALE_JOB_WATCHDOG_HEADROOM_MS. Set to 0 to disable this arm; processing
|
|
2527
|
+
* jobs use processingWatchdogMs.
|
|
2819
2528
|
*/
|
|
2820
2529
|
staleJobTimeoutMs?: number;
|
|
2821
2530
|
/**
|
|
@@ -2824,7 +2533,7 @@ interface JobManagerOpts {
|
|
|
2824
2533
|
*/
|
|
2825
2534
|
staleJobSweepIntervalMs?: number;
|
|
2826
2535
|
/**
|
|
2827
|
-
* Worker-liveness watchdog in ms
|
|
2536
|
+
* Worker-liveness watchdog in ms. Jobs in `processing`/`working`
|
|
2828
2537
|
* whose `lastActivityAt` is older than this are marked `failed` (dead
|
|
2829
2538
|
* worker), separately from the longer `awaiting-input` idle timeout above.
|
|
2830
2539
|
* Default: `descriptor.processingWatchdog * 1000` or
|
|
@@ -2834,15 +2543,15 @@ interface JobManagerOpts {
|
|
|
2834
2543
|
/**
|
|
2835
2544
|
* Interval at which locally-active `processing`/`working` jobs have their
|
|
2836
2545
|
* `lastActivityAt` bumped so the processing watchdog only fires on a truly
|
|
2837
|
-
* dead worker
|
|
2838
|
-
* `0` to disable (test seam). Also floors how far the
|
|
2546
|
+
* dead worker. Default: `DEFAULT_HEARTBEAT_INTERVAL_MS`. Set to
|
|
2547
|
+
* `0` to disable (test seam). Also floors how far the clock guard
|
|
2839
2548
|
* may compensate a backwards step on the processing arm — a cadence close to
|
|
2840
2549
|
* `processingWatchdogMs` leaves it no room and it compensates nothing.
|
|
2841
2550
|
*/
|
|
2842
2551
|
heartbeatIntervalMs?: number;
|
|
2843
2552
|
/**
|
|
2844
2553
|
* Age in ms at which a `pending` credit draw whose `job_id` matches no row in
|
|
2845
|
-
* the job store is released as orphaned
|
|
2554
|
+
* the job store is released as orphaned. The draw commits before
|
|
2846
2555
|
* the job row is persisted, so a crash or a fail-closed refusal in that
|
|
2847
2556
|
* window strands a hold no terminal path can ever reach. Must stay
|
|
2848
2557
|
* comfortably above the submit path's draw→persist latency. Set to `0` to
|
|
@@ -2853,7 +2562,7 @@ interface JobManagerOpts {
|
|
|
2853
2562
|
orphanDrawAgeMs?: number;
|
|
2854
2563
|
/**
|
|
2855
2564
|
* Age in ms at which a `pending` credit draw whose job row **is** terminal is
|
|
2856
|
-
* reconciled against that row's outcome
|
|
2565
|
+
* reconciled against that row's outcome — settled for a
|
|
2857
2566
|
* `completed` job, released for a `failed`/`cancelled` one. Covers the hole
|
|
2858
2567
|
* `orphanDrawAgeMs` cannot: `resolveCreditDraw` throwing on a ledger error
|
|
2859
2568
|
* leaves a terminal job whose hold no later path retries.
|
|
@@ -2861,7 +2570,7 @@ interface JobManagerOpts {
|
|
|
2861
2570
|
* Rides the same scan as the orphan arm, which has two consequences for the
|
|
2862
2571
|
* value you set here. Setting `orphanDrawAgeMs` to `0` disables this arm too.
|
|
2863
2572
|
* And the effective gate is `max(orphanDrawAgeMs, terminalDrawReconcileAgeMs)`
|
|
2864
|
-
*
|
|
2573
|
+
* the scan never returns a row younger than its own cutoff, so anything
|
|
2865
2574
|
* below `orphanDrawAgeMs` is silently clamped up to it. That floor is
|
|
2866
2575
|
* deliberate rather than a wart: honouring a narrower value would mean
|
|
2867
2576
|
* widening the scan, which hands the orphan arm rows younger than
|
|
@@ -2900,7 +2609,7 @@ interface JobManagerOpts {
|
|
|
2900
2609
|
/**
|
|
2901
2610
|
* Grace window in ms a reactivating machine waits for a live original handler
|
|
2902
2611
|
* to win the single-execution claim before replaying an `awaiting-input` job
|
|
2903
|
-
* itself
|
|
2612
|
+
* itself. Default: `DEFAULT_REACTIVATION_CLAIM_GRACE_MS`. Widen if
|
|
2904
2613
|
* cross-machine NOTIFY latency causes spurious double executions; lower in
|
|
2905
2614
|
* tests for speed.
|
|
2906
2615
|
*/
|
|
@@ -2908,7 +2617,7 @@ interface JobManagerOpts {
|
|
|
2908
2617
|
}
|
|
2909
2618
|
type ResolvedInput<I extends ZodLike | undefined> = I extends ZodLike<infer O> ? O : string;
|
|
2910
2619
|
/**
|
|
2911
|
-
* A deposit this payment funded that the job row did **not** take
|
|
2620
|
+
* A deposit this payment funded that the job row did **not** take.
|
|
2912
2621
|
*
|
|
2913
2622
|
* Reached when two mid-job top-ups race on a free-then-paid job: each opens its
|
|
2914
2623
|
* own implicit credit, and `verifyAndCredit`'s set-if-null bind keeps one. The
|
|
@@ -2958,12 +2667,13 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
2958
2667
|
private readonly cleanupTimers;
|
|
2959
2668
|
/** Cost revisions held only by a terminal handler owner until its durable merge succeeds. */
|
|
2960
2669
|
private readonly terminalCostHandoffRetryTimers;
|
|
2961
|
-
/** Per-job NOTIFY unsubscribe handles for the durable-status watch
|
|
2670
|
+
/** Per-job NOTIFY unsubscribe handles for the durable-status watch. */
|
|
2962
2671
|
private readonly statusWatchers;
|
|
2963
|
-
/** Job ids whose durable-status re-read is in flight — coalesces notify storms
|
|
2672
|
+
/** Job ids whose durable-status re-read is in flight — coalesces notify storms. */
|
|
2964
2673
|
private readonly statusChecksInFlight;
|
|
2965
|
-
/** Job ids that were notified mid-re-read and must be re-checked
|
|
2674
|
+
/** Job ids that were notified mid-re-read and must be re-checked. */
|
|
2966
2675
|
private readonly statusChecksQueued;
|
|
2676
|
+
private readonly inputResumeTails;
|
|
2967
2677
|
private readonly sessionId;
|
|
2968
2678
|
private nextJobId;
|
|
2969
2679
|
private accumulatorMonitor?;
|
|
@@ -2971,7 +2681,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
2971
2681
|
private readonly processingWatchdogMs;
|
|
2972
2682
|
private readonly reactivationClaimGraceMs;
|
|
2973
2683
|
/**
|
|
2974
|
-
* Worker-heartbeat cadence
|
|
2684
|
+
* Worker-heartbeat cadence. Also floors the clock
|
|
2975
2685
|
* guard's compensation on the processing arm — see
|
|
2976
2686
|
* {@link staleSweepCompensationCapMs}.
|
|
2977
2687
|
*/
|
|
@@ -2991,7 +2701,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
2991
2701
|
private jobRetentionResumeFrom?;
|
|
2992
2702
|
/**
|
|
2993
2703
|
* A `Date.now()` reading that cannot regress within this manager's lifetime
|
|
2994
|
-
*
|
|
2704
|
+
* Anchored at construction, so a manager already running when
|
|
2995
2705
|
* the clock stepped is covered. The stale-job sweep's two cutoffs, the
|
|
2996
2706
|
* orphan-draw age gate and the reactivation claim grace all read it; see
|
|
2997
2707
|
* {@link createMonotonicClock} for what it costs and where it is clamped.
|
|
@@ -3004,29 +2714,25 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3004
2714
|
private jobRetentionSweepInFlight;
|
|
3005
2715
|
private heartbeatInFlight;
|
|
3006
2716
|
/**
|
|
3007
|
-
* The denomination of every credit this DVM opens
|
|
2717
|
+
* The denomination of every credit this DVM opens. Resolved once,
|
|
3008
2718
|
* with the same boundary fallback `DVMServer` applies for direct JavaScript
|
|
3009
2719
|
* callers that force an incomplete value past the descriptor contract.
|
|
3010
2720
|
*/
|
|
3011
2721
|
private readonly pricingCurrency;
|
|
3012
2722
|
constructor(descriptor: DVMDescriptor<State, InputSchema>, opts: JobManagerOpts);
|
|
3013
|
-
/** Active in-memory jobs. */
|
|
3014
2723
|
get activeJobs(): Map<string, ServerJob>;
|
|
3015
|
-
/** The backing job store. */
|
|
3016
2724
|
get store(): JobStore;
|
|
3017
2725
|
/**
|
|
3018
|
-
* True when this DVM will actually issue receipts
|
|
2726
|
+
* True when this DVM will actually issue receipts — a key *and*
|
|
3019
2727
|
* a store that can allocate sequence numbers and persist bytes write-once.
|
|
3020
2728
|
* `/v1/info#receipts` reads this rather than the key alone, so the flag can
|
|
3021
2729
|
* never promise something `issueReceipt` silently declines to do.
|
|
3022
2730
|
*/
|
|
3023
2731
|
get receiptsEnabled(): boolean;
|
|
3024
|
-
/** True when the named capability exists on this descriptor. */
|
|
3025
2732
|
hasCapability(name: string): boolean;
|
|
3026
|
-
/** Names of every capability this descriptor exposes — used for error envelopes. */
|
|
3027
2733
|
capabilityNames(): string[];
|
|
3028
2734
|
/**
|
|
3029
|
-
* Resolve a capability's static `price` to msats
|
|
2735
|
+
* Resolve a capability's static `price` to msats.
|
|
3030
2736
|
*
|
|
3031
2737
|
* `"$X.XX"` is parsed as USD and converted via the SDK's shared fx fetcher.
|
|
3032
2738
|
* Returns `undefined` for dynamic-priced capabilities (those that declare
|
|
@@ -3035,7 +2741,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3035
2741
|
currentPriceMsats(capability: string): Promise<number | undefined>;
|
|
3036
2742
|
/**
|
|
3037
2743
|
* Translate a capability's static `price` into the fiat envelope used by
|
|
3038
|
-
* the per-capability `pricing.max` advertised on `/v1/info
|
|
2744
|
+
* the per-capability `pricing.max` advertised on `/v1/info`.
|
|
3039
2745
|
* Returns `undefined` for dynamic-priced capabilities and for unknown
|
|
3040
2746
|
* names (the route handler validates the name before invoking this).
|
|
3041
2747
|
*/
|
|
@@ -3044,24 +2750,24 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3044
2750
|
currency: string;
|
|
3045
2751
|
} | undefined;
|
|
3046
2752
|
/**
|
|
3047
|
-
* Parse a request body through a capability's input schema
|
|
2753
|
+
* Parse a request body through a capability's input schema.
|
|
3048
2754
|
*
|
|
3049
2755
|
* Resolution order when a capability declares an `input` schema:
|
|
3050
2756
|
* 1. `body.data` is preferred — agents pass structured fields directly
|
|
3051
|
-
*
|
|
2757
|
+
* (CLI builds this from `--param k=v`).
|
|
3052
2758
|
* 2. Fallback: `JSON.parse(body.input)` for clients still on the legacy
|
|
3053
|
-
*
|
|
2759
|
+
* JSON-string contract.
|
|
3054
2760
|
* 3. Neither usable → throw `MissingStructuredInputError` so the route can
|
|
3055
|
-
*
|
|
2761
|
+
* return `invalid_input` with a hint pointing at `/v1/info`.
|
|
3056
2762
|
*
|
|
3057
2763
|
* When the capability has no `input` schema, returns `body.input` raw
|
|
3058
2764
|
* (primitive path).
|
|
3059
2765
|
*
|
|
3060
|
-
* The
|
|
3061
|
-
* branches
|
|
2766
|
+
* The credit envelope is removed before the schema runs, on both
|
|
2767
|
+
* branches — it rides the signed body but is protocol-level, so a
|
|
3062
2768
|
* top-level `.strict()` capability schema would otherwise reject every
|
|
3063
2769
|
* explicit draw as an unknown key, before the envelope was even extracted.
|
|
3064
|
-
* The persisted `record.input` the
|
|
2770
|
+
* The persisted `record.input` the reactivation path re-reads is the
|
|
3065
2771
|
* pre-Zod wire form, so it comes through the second branch carrying them too.
|
|
3066
2772
|
*/
|
|
3067
2773
|
parseInput(body: {
|
|
@@ -3069,7 +2775,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3069
2775
|
data?: unknown;
|
|
3070
2776
|
}, capability: string): unknown;
|
|
3071
2777
|
/**
|
|
3072
|
-
* Look up the per-capability descriptor by name
|
|
2778
|
+
* Look up the per-capability descriptor by name. Throws when the
|
|
3073
2779
|
* name doesn't exist — the route layer validates body.capability first, so
|
|
3074
2780
|
* reaching this with an unknown name is a programmer error.
|
|
3075
2781
|
*/
|
|
@@ -3083,7 +2789,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3083
2789
|
*/
|
|
3084
2790
|
private getCapability;
|
|
3085
2791
|
/**
|
|
3086
|
-
* Allocate the id the next job will be created under
|
|
2792
|
+
* Allocate the id the next job will be created under. The
|
|
3087
2793
|
* submit path calls this BEFORE payment verification so the ledger draw
|
|
3088
2794
|
* commits with its job linkage, then passes the id back through
|
|
3089
2795
|
* `createJob`'s provenance. Ids allocated for requests whose payment is
|
|
@@ -3092,7 +2798,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3092
2798
|
*/
|
|
3093
2799
|
allocateJobId(): string;
|
|
3094
2800
|
/**
|
|
3095
|
-
* Create a new job from a request body. `provenance`
|
|
2801
|
+
* Create a new job from a request body. `provenance` is what the
|
|
3096
2802
|
* idempotent-replay path on `POST /v1/job` reads back: the raw `job_token`
|
|
3097
2803
|
* to re-issue, and the fingerprint + caller pubkey a retry must reproduce
|
|
3098
2804
|
* to be given it.
|
|
@@ -3107,32 +2813,32 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3107
2813
|
requestId?: string;
|
|
3108
2814
|
authRequestPath?: string;
|
|
3109
2815
|
/**
|
|
3110
|
-
* Pre-allocated id from {@link allocateJobId}
|
|
2816
|
+
* Pre-allocated id from {@link allocateJobId} — the submit
|
|
3111
2817
|
* path allocates before payment verification so the ledger draw can
|
|
3112
2818
|
* record the job it pays for.
|
|
3113
2819
|
*/
|
|
3114
2820
|
jobId?: string;
|
|
3115
2821
|
}): ServerJob;
|
|
3116
|
-
/** Build an SDKJobContext and attach it to the job. */
|
|
3117
2822
|
buildAndAttachContext(job: ServerJob, parsedInput: unknown): SDKJobContext<State, ResolvedInput<InputSchema>>;
|
|
3118
|
-
/** Start the handler for a job. */
|
|
3119
2823
|
runHandler(job: ServerJob, sdkCtx: SDKJobContext<unknown, unknown>): void;
|
|
3120
|
-
/** Dispatch a validated incoming message to the appropriate handler or pending resolver. */
|
|
3121
2824
|
dispatchMessage(job: ServerJob, type: MessageType, content: unknown): Promise<void>;
|
|
2825
|
+
private queueInputResume;
|
|
2826
|
+
private claimLocalInput;
|
|
2827
|
+
private dispatchMessageNow;
|
|
3122
2828
|
/**
|
|
3123
2829
|
* Persist a job to the backing store.
|
|
3124
2830
|
*
|
|
3125
|
-
* Awaits the per-job appender tail
|
|
3126
|
-
*
|
|
3127
|
-
*
|
|
3128
|
-
*
|
|
3129
|
-
*
|
|
3130
|
-
*
|
|
3131
|
-
*
|
|
2831
|
+
* Awaits the per-job appender tail so that:
|
|
2832
|
+
* 1. The snapshot's `messages` is consistent with the `job_messages` table
|
|
2833
|
+
* no row is still in flight at snapshot time.
|
|
2834
|
+
* 2. For terminal saves, the `DELETE FROM job_messages` inside `save()`
|
|
2835
|
+
* can't race a still-pending `appendOutgoing` for the final yield
|
|
2836
|
+
* message (which would otherwise wipe the row before an in-flight SSE
|
|
2837
|
+
* subscriber's NOTIFY-driven fetch can see it).
|
|
3132
2838
|
*/
|
|
3133
2839
|
persistJob(job: ServerJob): Promise<void>;
|
|
3134
2840
|
/**
|
|
3135
|
-
* Cross-machine terminal guard
|
|
2841
|
+
* Cross-machine terminal guard. `buildContext`'s guard reads the
|
|
3136
2842
|
* in-memory `job.status`, so on its own it only protects the machine running
|
|
3137
2843
|
* the handler. On a multi-machine DVM a `DELETE /v1/job/:id` routinely lands
|
|
3138
2844
|
* on a machine that isn't running the job — `app.ts` cancels it in the store
|
|
@@ -3144,7 +2850,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3144
2850
|
* fires `job.abort` so in-flight `ctx.fetch` calls tear down. Returns true when
|
|
3145
2851
|
* the durable state won and the caller must skip its save.
|
|
3146
2852
|
*
|
|
3147
|
-
* Two triggers: `watchDurableStatus`'s NOTIFY subscription
|
|
2853
|
+
* Two triggers: `watchDurableStatus`'s NOTIFY subscription fires
|
|
3148
2854
|
* this within a round-trip of the remote cancel committing, which is what
|
|
3149
2855
|
* actually stops the provider spend; `persistJob` calls it again on every save
|
|
3150
2856
|
* as the backstop for the window where a cancel commits between a handler's
|
|
@@ -3163,7 +2869,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3163
2869
|
/** Durably merge a terminal handler owner's latest cost before reporting it. */
|
|
3164
2870
|
private persistAdoptedTerminalCost;
|
|
3165
2871
|
/**
|
|
3166
|
-
* Watch the durable status of a locally-active job
|
|
2872
|
+
* Watch the durable status of a locally-active job.
|
|
3167
2873
|
*
|
|
3168
2874
|
* `ctx.signal` is raised by `dispatchMessage`, which only runs on the machine
|
|
3169
2875
|
* holding the job in `activeJobs`. On a multi-machine DVM the `DELETE` usually
|
|
@@ -3181,30 +2887,30 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3181
2887
|
*/
|
|
3182
2888
|
private watchDurableStatus;
|
|
3183
2889
|
/**
|
|
3184
|
-
* Tear down a job's durable-status subscription
|
|
2890
|
+
* Tear down a job's durable-status subscription. Must be called
|
|
3185
2891
|
* wherever a job leaves `activeJobs` — a leaked subscriber outlives the job on
|
|
3186
2892
|
* the store's shared LISTEN connection. Idempotent.
|
|
3187
2893
|
*/
|
|
3188
2894
|
unwatchDurableStatus(jobId: string): void;
|
|
3189
2895
|
/**
|
|
3190
|
-
* NOTIFY-driven durable-status re-read, coalesced per job
|
|
2896
|
+
* NOTIFY-driven durable-status re-read, coalesced per job.
|
|
3191
2897
|
*
|
|
3192
2898
|
* A notify that arrives while a re-read is in flight is queued rather than
|
|
3193
2899
|
* dropped: the in-flight read may have observed the row a moment *before* the
|
|
3194
|
-
* cancel committed, and dropping its notify would
|
|
3195
|
-
*
|
|
2900
|
+
* cancel committed, and dropping its notify would leave the local handler
|
|
2901
|
+
* running until its next store write instead of aborting promptly.
|
|
3196
2902
|
*/
|
|
3197
2903
|
private checkDurableTerminal;
|
|
3198
2904
|
/**
|
|
3199
2905
|
* Wire the Tx A appender when the store supports streaming.
|
|
3200
2906
|
*
|
|
3201
|
-
* Tx A
|
|
2907
|
+
* Tx A: outgoing message + `pending_payment_msats` bump land in
|
|
3202
2908
|
* the same transaction. The DB allocates the row's seq via
|
|
3203
2909
|
* `UPDATE jobs SET next_seq = next_seq + 1 RETURNING` so concurrent inbound
|
|
3204
2910
|
* traffic on different machines can never collide on the `(job_id, seq)` PK.
|
|
3205
2911
|
* In-memory `job.seq` keeps its own counter for same-machine SSE listeners.
|
|
3206
2912
|
*
|
|
3207
|
-
* Per-job serialisation
|
|
2913
|
+
* Per-job serialisation: chained through `job.messageAppenderTail`
|
|
3208
2914
|
* so two synchronous `providerMessage` calls (e.g. `artifact` followed by
|
|
3209
2915
|
* `complete`) commit their `appendOutgoing` transactions in call order.
|
|
3210
2916
|
* Without this, the two transactions race for the `jobs` row lock and the
|
|
@@ -3215,19 +2921,17 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3215
2921
|
* `artifact`'s NOTIFY silently drops the artifact.
|
|
3216
2922
|
*/
|
|
3217
2923
|
private wireMessageAppender;
|
|
3218
|
-
/** Start (or restart) the idle timer for a job. */
|
|
3219
2924
|
startIdleTimer(job: ServerJob): void;
|
|
3220
|
-
/** Clear the idle timer for a job. */
|
|
3221
2925
|
clearIdleTimer(id: string): void;
|
|
3222
|
-
/** Cancel a job due to idle timeout. Exposed for the reactivation path's idle-expiry guard
|
|
2926
|
+
/** Cancel a job due to idle timeout. Exposed for the reactivation path's idle-expiry guard. */
|
|
3223
2927
|
cancelJobIdle(job: ServerJob): Promise<void>;
|
|
3224
2928
|
/**
|
|
3225
2929
|
* Force-terminate stale non-terminal jobs. Two status-aware arms:
|
|
3226
|
-
*
|
|
3227
|
-
*
|
|
3228
|
-
*
|
|
3229
|
-
*
|
|
3230
|
-
*
|
|
2930
|
+
* • `awaiting-input` past `staleJobTimeoutMs` → `cancelled`
|
|
2931
|
+
* (`stale_no_terminal_status`) — the caller never paid / responded.
|
|
2932
|
+
* • `processing`/`working` past `processingWatchdogMs` → `failed`
|
|
2933
|
+
* (`worker_died_mid_job`) — the worker died mid-job. The heartbeat keeps
|
|
2934
|
+
* live workers fresh, so a stale row here means a dead process.
|
|
3231
2935
|
*
|
|
3232
2936
|
* Fires on a timer (wired in the constructor) and also callable on demand
|
|
3233
2937
|
* (tests, ops). The CAS in `cancelStaleJob` makes this safe to run
|
|
@@ -3236,17 +2940,17 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3236
2940
|
* descriptors — the local activeJobs teardown only makes sense on the
|
|
3237
2941
|
* JobManager that actually hosted the zombie handler.
|
|
3238
2942
|
*
|
|
3239
|
-
* **Both cutoffs float off the wall clock
|
|
2943
|
+
* **Both cutoffs float off the wall clock.** Each is one
|
|
3240
2944
|
* `Date.now()` sample compared against `last_activity_at`, stamped from a
|
|
3241
2945
|
* different sample at a different moment, so a backwards step between the
|
|
3242
2946
|
* two makes every row look more recently active than it is: the query
|
|
3243
2947
|
* matches nothing and both arms go silent for the length of the skew. The
|
|
3244
2948
|
* dead worker's job keeps its `processing` status, its credit hold stays
|
|
3245
|
-
* `pending`, and the
|
|
3246
|
-
* never fires. Same defect, same host class, as
|
|
2949
|
+
* `pending`, and the paid-job-death alert — wired to this reaper —
|
|
2950
|
+
* never fires. Same defect, same host class, as orphan sweep.
|
|
3247
2951
|
*
|
|
3248
2952
|
* **The compensation is capped** — see {@link staleSweepCompensationCapMs}
|
|
3249
|
-
* for where each arm's cap comes from — which
|
|
2953
|
+
* for where each arm's cap comes from — which did not need to do. Its eagerness costs at worst an early release
|
|
3250
2954
|
* of an unclaimed hold, refused outright for any live job row; ours
|
|
3251
2955
|
* force-*fails* a running job. `cancelStaleJob`'s CAS does not cover that:
|
|
3252
2956
|
* `expectedActivityBefore` is the same compensated threshold the query used,
|
|
@@ -3278,7 +2982,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3278
2982
|
}>;
|
|
3279
2983
|
/**
|
|
3280
2984
|
* How far one watchdog arm may lean on {@link monotonicNowMs} past the wall
|
|
3281
|
-
* clock to cover a backwards step
|
|
2985
|
+
* clock to cover a backwards step. The compensation is capped,
|
|
3282
2986
|
* not free: a claimed row must still have been silent for
|
|
3283
2987
|
* `windowMs - cap`, and unlike the orphan sweep, claiming eagerly here
|
|
3284
2988
|
* force-fails a job that may well be alive.
|
|
@@ -3298,7 +3002,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3298
3002
|
* clock stepped. That pair is marginal already; the guard must not make it
|
|
3299
3003
|
* deterministic. The cap collapses to zero there, which is today's
|
|
3300
3004
|
* behaviour: late, never wrong. The `awaiting-input` arm takes no such term
|
|
3301
|
-
*
|
|
3005
|
+
* nothing heartbeats it by design, and its refresh is an inbound caller
|
|
3302
3006
|
* message on no cadence at all.
|
|
3303
3007
|
*
|
|
3304
3008
|
* **Why a magnitude and not a predicate.** The tempting sharper rule is to
|
|
@@ -3335,7 +3039,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3335
3039
|
* Resolve `pending` credit draws the terminal funnel can no longer reach.
|
|
3336
3040
|
* Two arms over one scan, distinguished by whether the draw's job row exists.
|
|
3337
3041
|
*
|
|
3338
|
-
* **No job row
|
|
3042
|
+
* **No job row — release.** allocates the job id and
|
|
3339
3043
|
* commits the ledger draw, with its `job_id` linkage, before `recordReplay`
|
|
3340
3044
|
* and `persistJob` write the job row. A crash, a `draw_conflict` 409, or a
|
|
3341
3045
|
* fail-closed `replay_detected` 401 in that window leaves a committed hold
|
|
@@ -3345,12 +3049,12 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3345
3049
|
* available balance stays reduced forever. Cosmetic for an implicit N=1
|
|
3346
3050
|
* credit, a silent balance shrink for an explicit N>1 one.
|
|
3347
3051
|
*
|
|
3348
|
-
* **Terminal job row
|
|
3052
|
+
* **Terminal job row — reconcile to its outcome.** The funnel
|
|
3349
3053
|
* demonstrably fails: `resolveCreditDraw` throws on a ledger/DB error,
|
|
3350
3054
|
* `issueReceipt` catches it, logs `credit_resolve_failed` and returns
|
|
3351
3055
|
* nothing, and until now nothing retried. Same stranded hold, reached through
|
|
3352
3056
|
* a different door — and for a `completed` job it strands the platform's
|
|
3353
|
-
* books too, since draws *are* the revenue events
|
|
3057
|
+
* books too, since draws *are* the revenue events: the caller is
|
|
3354
3058
|
* charged nothing, the balance never moves, and outstanding liability
|
|
3355
3059
|
* (`deposits − draw revenue`) overstates forever. So a late settle books, off
|
|
3356
3060
|
* the committed draw row, through the ordinary reporter path.
|
|
@@ -3377,8 +3081,8 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3377
3081
|
reconciled: number;
|
|
3378
3082
|
}>;
|
|
3379
3083
|
/**
|
|
3380
|
-
* The end-of-scan line for a tick that resolved nothing
|
|
3381
|
-
*
|
|
3084
|
+
* The end-of-scan line for a tick that resolved nothing. Two shapes are
|
|
3085
|
+
* worth a warning, and neither is the ordinary
|
|
3382
3086
|
* one — the timer runs every 5 minutes per ledger in production.
|
|
3383
3087
|
*
|
|
3384
3088
|
* `examined > 0`: aged candidates were looked at and none moved. Usually
|
|
@@ -3387,7 +3091,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3387
3091
|
*
|
|
3388
3092
|
* `examined === 0`: the query returned nothing. Ordinarily that means there
|
|
3389
3093
|
* is nothing to do — but it is equally what a blinded scan looks like, which
|
|
3390
|
-
* is the case the
|
|
3094
|
+
* is the case the gate could not reach. Probe for the oldest
|
|
3391
3095
|
* `pending` hold at any age and speak up when one exists the scan should
|
|
3392
3096
|
* have seen and didn't: already past the age gate outright, stamped ahead of
|
|
3393
3097
|
* our clock (some other process's clock is fast), or hidden while our own
|
|
@@ -3400,7 +3104,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3400
3104
|
*/
|
|
3401
3105
|
private reportSweepOutcome;
|
|
3402
3106
|
/**
|
|
3403
|
-
* Resolve one aged `pending` draw whose job row reached terminal
|
|
3107
|
+
* Resolve one aged `pending` draw whose job row reached terminal,
|
|
3404
3108
|
* mirroring what `resolveCreditDraw` would have done in line. Returns true
|
|
3405
3109
|
* when this call is the one that moved the draw — the caller counts it, and
|
|
3406
3110
|
* only it books.
|
|
@@ -3434,7 +3138,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3434
3138
|
/**
|
|
3435
3139
|
* Refresh `lastActivityAt` for jobs this process is actively running
|
|
3436
3140
|
* (`processing`/`working`) so the processing watchdog only fires once the
|
|
3437
|
-
* worker is genuinely dead
|
|
3141
|
+
* worker is genuinely dead. `awaiting-input` jobs are
|
|
3438
3142
|
* deliberately excluded — their idle timeout must still elapse. Fires on the
|
|
3439
3143
|
* heartbeat timer; also callable on demand for tests.
|
|
3440
3144
|
*/
|
|
@@ -3442,15 +3146,15 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3442
3146
|
beat: number;
|
|
3443
3147
|
}>;
|
|
3444
3148
|
/**
|
|
3445
|
-
* Single-execution claim for a reactivating machine
|
|
3149
|
+
* Single-execution claim for a reactivating machine. Before
|
|
3446
3150
|
* replaying an `awaiting-input` job, wait a bounded grace window for a live
|
|
3447
3151
|
* original handler to win the `awaiting-input → processing` CAS via its
|
|
3448
3152
|
* NOTIFY wake. Resolves:
|
|
3449
|
-
*
|
|
3450
|
-
*
|
|
3451
|
-
*
|
|
3452
|
-
*
|
|
3453
|
-
*
|
|
3153
|
+
* • `false` — the row left `awaiting-input` during the window (the original
|
|
3154
|
+
* handler claimed it / drove it terminal). Stand down; do not replay.
|
|
3155
|
+
* • `true` — the window elapsed still `awaiting-input` (the original worker
|
|
3156
|
+
* is gone or has no live resolver) AND this machine won the claim CAS.
|
|
3157
|
+
* Replay for dead-worker recovery.
|
|
3454
3158
|
*
|
|
3455
3159
|
* Biasing the original handler to win kills the double execution (double
|
|
3456
3160
|
* substrate spend, clobbered artifact) without heartbeating `awaiting-input`
|
|
@@ -3459,7 +3163,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3459
3163
|
*/
|
|
3460
3164
|
claimAwaitingInputForReactivation(jobId: string): Promise<boolean>;
|
|
3461
3165
|
/**
|
|
3462
|
-
* Record an inbound client message durably via Tx B
|
|
3166
|
+
* Record an inbound client message durably via Tx B and append
|
|
3463
3167
|
* it to the in-memory `job.messages` array. Returns the DB-allocated seq
|
|
3464
3168
|
* (`null` when the store isn't streamable — non-Postgres test fallback).
|
|
3465
3169
|
*
|
|
@@ -3478,13 +3182,13 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3478
3182
|
* in-memory `ServerJob`, wires the appender, builds the context, starts
|
|
3479
3183
|
* the idle timer. The caller has already recorded the inbound via Tx B
|
|
3480
3184
|
* and run Tx C (verify + credit); reactivation is now decoupled from
|
|
3481
|
-
* credit
|
|
3185
|
+
* credit. The caller decides whether to run the
|
|
3482
3186
|
* handler (Pattern A: status was `awaiting-input`) or to dispatch the
|
|
3483
3187
|
* message to a custom handler (Pattern B).
|
|
3484
3188
|
*
|
|
3485
3189
|
* For DVMs that declare descriptor-level auth, the persisted
|
|
3486
3190
|
* `record.input` carries the original signed envelope. We re-verify the
|
|
3487
|
-
* signature here
|
|
3191
|
+
* signature here so a DB-layer tamper — compromised admin, SQL
|
|
3488
3192
|
* injection, malicious operator with DB access — can't silently feed an
|
|
3489
3193
|
* attacker-supplied pubkey/envelope into the handler. The drift window
|
|
3490
3194
|
* and replay store are deliberately skipped: the persisted timestamp is
|
|
@@ -3498,7 +3202,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3498
3202
|
* What gets re-verified is the persisted row read back through
|
|
3499
3203
|
* {@link signedRequestInput} — `app.ts` stores the pre-Zod wire form, so
|
|
3500
3204
|
* those are the caller's own signed bytes, the same ones `/v1/quote` and the
|
|
3501
|
-
* submit checked
|
|
3205
|
+
* submit checked. That makes the tamper check strictly stronger
|
|
3502
3206
|
* than the parsed form it replaced: an injected key the capability schema
|
|
3503
3207
|
* doesn't declare used to be stripped before the signature was checked, so
|
|
3504
3208
|
* it re-verified clean.
|
|
@@ -3508,7 +3212,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3508
3212
|
sdkCtx: SDKJobContext<unknown, unknown>;
|
|
3509
3213
|
};
|
|
3510
3214
|
/**
|
|
3511
|
-
* Verify an inbound payment and apply the credit via Tx C
|
|
3215
|
+
* Verify an inbound payment and apply the credit via Tx C.
|
|
3512
3216
|
* The inbound message must have been recorded via Tx B already
|
|
3513
3217
|
* (`recordInboundPending` returned `inboundSeq`). For non-streamable
|
|
3514
3218
|
* stores (`inboundSeq === null`), falls back to in-memory mutation via
|
|
@@ -3518,12 +3222,12 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3518
3222
|
* `pendingPaymentMsats` without external verification. Gated on
|
|
3519
3223
|
* `devModeSkipsPaymentVerification` — the same predicate the upfront path
|
|
3520
3224
|
* reads, so a dev server wired to a mint verifies mid-job payments for real
|
|
3521
|
-
*
|
|
3225
|
+
* plus the explicit `dev_auto` opt-out the dev console uses,
|
|
3522
3226
|
* which is the one caller that has no wallet to pay from. `dev_auto` loses to
|
|
3523
3227
|
* any proof riding the same message: money on the wire always takes the rail,
|
|
3524
3228
|
* so the flag can never leave a real token unspent against a credited job.
|
|
3525
3229
|
*
|
|
3526
|
-
* Tx C is not atomic with the rail commit
|
|
3230
|
+
* Tx C is not atomic with the rail commit. `verifyIncomingPayment`
|
|
3527
3231
|
* commits the proofs *and* the ledger leg in one transaction and returns;
|
|
3528
3232
|
* only then does `verifyAndCredit` write the job row, on its own connection.
|
|
3529
3233
|
* Everything after that call therefore runs with the caller's money already
|
|
@@ -3539,7 +3243,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3539
3243
|
mintHealthTracker?: MintHealthTracker;
|
|
3540
3244
|
}): Promise<ProcessPaymentOutcome>;
|
|
3541
3245
|
/**
|
|
3542
|
-
* Tx C with a bounded retry
|
|
3246
|
+
* Tx C with a bounded retry.
|
|
3543
3247
|
*
|
|
3544
3248
|
* By the time this runs on the verified path, the rail commit and the ledger
|
|
3545
3249
|
* leg have already committed in a transaction this call is not part of — so a
|
|
@@ -3573,7 +3277,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3573
3277
|
private creditInbound;
|
|
3574
3278
|
/**
|
|
3575
3279
|
* Answer a payment whose rail and ledger legs committed but whose job row
|
|
3576
|
-
* could not be written
|
|
3280
|
+
* could not be written — every retry spent, the money real.
|
|
3577
3281
|
*
|
|
3578
3282
|
* The refusal names the credit the money landed on so the caller can act on
|
|
3579
3283
|
* it directly, mirroring the short mid-job pay's 402 (`payment.ts`). It is a
|
|
@@ -3582,7 +3286,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3582
3286
|
* fault an operator should see in their 5xx rate. `retryable` is true because
|
|
3583
3287
|
* the ask is genuinely still outstanding.
|
|
3584
3288
|
*
|
|
3585
|
-
* Nothing is released here. The hold is left to the
|
|
3289
|
+
* Nothing is released here. The hold is left to the reconciler,
|
|
3586
3290
|
* which makes the same decision — unbound draw ⇒ release, never settle — but
|
|
3587
3291
|
* under the credit row's `FOR UPDATE`, after the job is terminal, where it
|
|
3588
3292
|
* cannot race an in-flight COMMIT. Releasing from here would also be
|
|
@@ -3605,23 +3309,23 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3605
3309
|
private reportUnappliedPayment;
|
|
3606
3310
|
/**
|
|
3607
3311
|
* Describe a deposit the job row never took, in the shape both the 201 and
|
|
3608
|
-
* the `payment_unapplied` 500 carry
|
|
3312
|
+
* the `payment_unapplied` 500 carry. `undefined` when the payment
|
|
3609
3313
|
* ran ledger-less (no credit ledger wired, or the top-up preflight skipped) —
|
|
3610
3314
|
* there is no credit to name, so the 201 carries no `credit` block and the
|
|
3611
3315
|
* 500 takes its words from `unappliedCopy` directly.
|
|
3612
3316
|
*/
|
|
3613
3317
|
private unappliedCredit;
|
|
3614
|
-
/** Book a mid-job top-up's funding as a deposit (
|
|
3318
|
+
/** Book a mid-job top-up's funding as a deposit (the deposit-accounting rule). */
|
|
3615
3319
|
private reportCreditDeposit;
|
|
3616
3320
|
/**
|
|
3617
3321
|
* Internal: build the BuildContextOpts shared between `createJob` and
|
|
3618
3322
|
* `reactivateJob`. Centralised so persistence/terminal callbacks and the
|
|
3619
|
-
*
|
|
3323
|
+
* NOTIFY-driven cross-machine wake stay in one place.
|
|
3620
3324
|
*/
|
|
3621
3325
|
private buildContextOpts;
|
|
3622
3326
|
/**
|
|
3623
3327
|
* Internal: what unit `ctx.requestPayment`'s auto-credit gate may measure this
|
|
3624
|
-
* job's remaining pool in
|
|
3328
|
+
* job's remaining pool in.
|
|
3625
3329
|
*
|
|
3626
3330
|
* The regime is read off the DRAW, never off the job. Implicit N=1 is exactly
|
|
3627
3331
|
* `credit_id === imp:<rail>:<draw_id>`, because `fundAndDraw` derives both
|
|
@@ -3639,15 +3343,14 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3639
3343
|
* drew and whose pool this can't read or denominate answers `unpriced`, which
|
|
3640
3344
|
* the gate declines to auto-credit from: the msat figure it would otherwise
|
|
3641
3345
|
* fall back on is a slice of the credit's rail value at a ratio pinned
|
|
3642
|
-
* whenever that credit was funded,
|
|
3643
|
-
* to stop spending against.
|
|
3346
|
+
* whenever that credit was funded, so it cannot safely fund a new ask.
|
|
3644
3347
|
*/
|
|
3645
3348
|
private resolveDrawPool;
|
|
3646
3349
|
/** Internal: structured warn for the one path that can't price a drawn job's pool. */
|
|
3647
3350
|
private warnDrawPool;
|
|
3648
3351
|
/**
|
|
3649
3352
|
* Issue the signed receipt for a job that has reached a terminal status
|
|
3650
|
-
*
|
|
3353
|
+
* Idempotent and safe to call from every terminal path — the
|
|
3651
3354
|
* store owns both the sequence allocation and the write-once persist, so
|
|
3652
3355
|
* two machines racing on the same job converge on identical bytes and burn
|
|
3653
3356
|
* exactly one sequence number.
|
|
@@ -3663,10 +3366,10 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3663
3366
|
/** Build the common settle/release args, adding the hosted release outbox when wired. */
|
|
3664
3367
|
private drawResolutionArgs;
|
|
3665
3368
|
/**
|
|
3666
|
-
* Settle or release a terminal job's credit draw
|
|
3369
|
+
* Settle or release a terminal job's credit draw: `completed`
|
|
3667
3370
|
* settles (the hold becomes a real debit); `failed`/`cancelled` releases
|
|
3668
3371
|
* (the hold evaporates — "no debit on job failure", mechanically
|
|
3669
|
-
* superseding the
|
|
3372
|
+
* superseding the 974 no-op refund for credit-paid jobs, including
|
|
3670
3373
|
* the `ctx.fail` path and the stale-sweeper reap). Idempotent — a replayed
|
|
3671
3374
|
* resolution returns the recorded state. Returns the `ReceiptCredit` block
|
|
3672
3375
|
* for the receipt: `balance_after` is the recorded draw trajectory for a
|
|
@@ -3674,7 +3377,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3674
3377
|
*/
|
|
3675
3378
|
private resolveCreditDraw;
|
|
3676
3379
|
/**
|
|
3677
|
-
* The one place a terminal job's status becomes a ledger verb
|
|
3380
|
+
* The one place a terminal job's status becomes a ledger verb:
|
|
3678
3381
|
* `completed` settles the hold into a real debit, `failed`/`cancelled`
|
|
3679
3382
|
* release it. Read by the in-line funnel (`resolveCreditDraw`) and by the
|
|
3680
3383
|
* late reconciler (`reconcileTerminalDraw`) — two paths that must never
|
|
@@ -3688,32 +3391,26 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3688
3391
|
*/
|
|
3689
3392
|
private attachReceipt;
|
|
3690
3393
|
/**
|
|
3691
|
-
* Read-path repair for a terminal job carrying no receipt
|
|
3394
|
+
* Read-path repair for a terminal job carrying no receipt, for
|
|
3692
3395
|
* either an in-memory job or a store record. Free in the steady state — it
|
|
3693
3396
|
* returns on the `receipt` check without touching the store — so it costs
|
|
3694
3397
|
* only on the cases it exists for:
|
|
3695
3398
|
*
|
|
3696
3399
|
* - **A crash between the two store calls.** `claimReceiptSeq` commits the
|
|
3697
|
-
*
|
|
3698
|
-
*
|
|
3699
|
-
*
|
|
3700
|
-
*
|
|
3701
|
-
*
|
|
3702
|
-
*
|
|
3400
|
+
* sequence number to the job row before `saveReceipt` writes the bytes; a
|
|
3401
|
+
* process death in that window would otherwise strand that number
|
|
3402
|
+
* forever, and a permanent gap is indistinguishable from the deliberate
|
|
3403
|
+
* suppression `seq` exists to expose. Re-issuing here reuses the already
|
|
3404
|
+
* committed number (the claim is idempotent per job) rather than
|
|
3405
|
+
* allocating a second one.
|
|
3703
3406
|
* - **A transient store failure** at the terminal: `issueReceipt` logs
|
|
3704
|
-
*
|
|
3705
|
-
*
|
|
3407
|
+
* `receipt_issue_failed` and returns nothing rather than failing the job,
|
|
3408
|
+
* so the next read retries.
|
|
3706
3409
|
* - **Jobs that terminated before the DVM had a receipt key.** They pick one
|
|
3707
|
-
*
|
|
3410
|
+
* up on first read, with `issued_at` reflecting when it was signed.
|
|
3708
3411
|
*/
|
|
3709
3412
|
ensureReceipt(target: ServerJob | JobRecord): Promise<void>;
|
|
3710
|
-
/**
|
|
3711
|
-
* Close out a terminal job whose caller hasn't already persisted it: sign
|
|
3712
|
-
* the receipt, then save the snapshot. The snapshot save never writes the
|
|
3713
|
-
* receipt column, so ordering only affects how soon a reader sees the
|
|
3714
|
-
* receipt — this way a caller polling immediately after the terminal
|
|
3715
|
-
* already finds it.
|
|
3716
|
-
*/
|
|
3413
|
+
/** Commit the terminal snapshot before resolving money and signing its durable evidence. */
|
|
3717
3414
|
private finalizeTerminal;
|
|
3718
3415
|
/**
|
|
3719
3416
|
* Issue a receipt for a job this process doesn't hold in `activeJobs` — a
|
|
@@ -3724,10 +3421,12 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3724
3421
|
issueReceiptForStoredJob(jobId: string): Promise<JobReceipt | undefined>;
|
|
3725
3422
|
/**
|
|
3726
3423
|
* Finish accounting for a terminal written directly to the store — the
|
|
3727
|
-
* stale reaper and a cancel handled on a different machine
|
|
3424
|
+
* stale reaper and a cancel handled on a different machine.
|
|
3728
3425
|
*/
|
|
3729
3426
|
finalizeStoredTerminal(jobId: string): Promise<JobReceipt | undefined>;
|
|
3730
|
-
|
|
3427
|
+
private reportTerminalJob;
|
|
3428
|
+
private terminalReportRecoveryCursor?;
|
|
3429
|
+
private recoverTerminalReports;
|
|
3731
3430
|
private handleJobTerminal;
|
|
3732
3431
|
/**
|
|
3733
3432
|
* Re-enqueue terminal cost-only rows left between the job commit and outbox
|
|
@@ -3744,11 +3443,11 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3744
3443
|
/** Report the latest declared-cost state only when no revenue event carried it. */
|
|
3745
3444
|
private reportJobCost;
|
|
3746
3445
|
/**
|
|
3747
|
-
* Report a completed paid job's revenue
|
|
3446
|
+
* Report a completed paid job's revenue under its durable job identity.
|
|
3748
3447
|
* Fire-and-forget — the `RevenueReporter` owns persistence and retry.
|
|
3749
3448
|
*
|
|
3750
3449
|
* For a **credit-backed** job the revenue event is the *settled draw* (spec
|
|
3751
|
-
* §10), and since
|
|
3450
|
+
* §10), and since the behavior was introduced the sats figure corrects the job's mirror of that
|
|
3752
3451
|
* draw against the draw itself: `withDrawBasis` stamps the job when the
|
|
3753
3452
|
* payment lands, but a Bitcoin credit's settle re-prices the draw against
|
|
3754
3453
|
* the funding lots it consumed. The correction is a delta, because a job's
|
|
@@ -3763,7 +3462,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3763
3462
|
* whose price couldn't be fiat-denominated) nothing changes: the pre-credits
|
|
3764
3463
|
* payload is reported verbatim.
|
|
3765
3464
|
*
|
|
3766
|
-
* Takes the fields rather than a `ServerJob` so the
|
|
3465
|
+
* Takes the fields rather than a `ServerJob` so the reconciler — a
|
|
3767
3466
|
* background sweep that holds no in-process job — books through this exact
|
|
3768
3467
|
* path off the row it read. `paymentTxHash` is passed **verbatim**, never
|
|
3769
3468
|
* recomputed as a `drawSettlementRef`: it is the same column the in-line
|
|
@@ -3780,7 +3479,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3780
3479
|
* DVM with no reporter). Every drop that costs a booking logs first.
|
|
3781
3480
|
*
|
|
3782
3481
|
* The rail check sits **below** the draw re-read, not in the entry guard
|
|
3783
|
-
*
|
|
3482
|
+
* a settled draw the terminal can't report under is a real debit
|
|
3784
3483
|
* with no revenue event, permanently overstating outstanding liability
|
|
3785
3484
|
* (`deposits − draw revenue`), and it used to return here in silence. Placed
|
|
3786
3485
|
* after the re-read, `revenue_skipped_no_rail` can name the draw and its
|
|
@@ -3789,19 +3488,16 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
|
|
|
3789
3488
|
private bookRevenue;
|
|
3790
3489
|
/** Schedule job removal from activeJobs after a delay so clients can still poll final status. */
|
|
3791
3490
|
scheduleCleanup(job: ServerJob): void;
|
|
3792
|
-
/** Clear all timers. Called on server shutdown to allow clean exit. */
|
|
3793
3491
|
shutdown(): void;
|
|
3794
3492
|
}
|
|
3795
3493
|
|
|
3796
3494
|
/** Cached liveness signal for a platform-hosted DVM's Lightning receive rail. */
|
|
3797
3495
|
interface LightningRailHealth {
|
|
3798
|
-
/** Whether the receive rail may be advertised at this instant. */
|
|
3799
3496
|
available(): boolean;
|
|
3800
3497
|
/** Prime the cached signal at boot when the source supports it. */
|
|
3801
3498
|
refresh?(): Promise<void>;
|
|
3802
3499
|
}
|
|
3803
3500
|
|
|
3804
|
-
/** Builder-facing wiring for the Lightning receive leg (internal-review). */
|
|
3805
3501
|
interface LightningReceiveConfig {
|
|
3806
3502
|
/**
|
|
3807
3503
|
* Receive-only NWC connection URI (`DVMKIT_NWC_RECEIVE_URI`). Validated at
|
|
@@ -3835,31 +3531,19 @@ interface LightningReceiveConfig {
|
|
|
3835
3531
|
declare const DEFAULT_INVOICE_TTL_SECONDS = 900;
|
|
3836
3532
|
/**
|
|
3837
3533
|
* Floor on the invoice lifetime: twice the signed-request drift window, so the
|
|
3838
|
-
* bolt11 always outlives the funding negotiation that produced it
|
|
3839
|
-
* "invoice expiry ≥ the funding-negotiation window"). A shorter one would
|
|
3534
|
+
* bolt11 always outlives the funding negotiation that produced it. A shorter one would
|
|
3840
3535
|
* expire inside the caller's own retry budget and strand them mid-top-up.
|
|
3841
3536
|
*/
|
|
3842
3537
|
declare const MIN_INVOICE_TTL_SECONDS = 600;
|
|
3843
3538
|
/**
|
|
3844
|
-
*
|
|
3845
|
-
*
|
|
3846
|
-
*
|
|
3847
|
-
*
|
|
3848
|
-
* settlement. Two properties are load-bearing:
|
|
3539
|
+
* Prepaid Lightning funding over a receive-safe NWC connection. URI construction validates
|
|
3540
|
+
* the advertised method allowlist, refusing spending or unknown authority; the injected
|
|
3541
|
+
* backend seam bypasses that probe. Receive-safe methods include invoice, read and
|
|
3542
|
+
* hold-invoice operations.
|
|
3849
3543
|
*
|
|
3850
|
-
*
|
|
3851
|
-
*
|
|
3852
|
-
*
|
|
3853
|
-
* connection by rule.
|
|
3854
|
-
* - **Crediting is pull-based — there is no settlement watcher.** The caller's
|
|
3855
|
-
* next request drives `lookup_invoice`, which is what makes this work on a
|
|
3856
|
-
* suspend-to-zero fleet: a machine that is asleep has nothing to miss.
|
|
3857
|
-
* Wallet downtime at that moment delays crediting; it never loses money,
|
|
3858
|
-
* because the invoice→credit binding is a durable row.
|
|
3859
|
-
*
|
|
3860
|
-
* Reachability is **cached**, never probed per request: the funding menu is
|
|
3861
|
-
* assembled on every quote and every 402, so a live NIP-47 round trip there
|
|
3862
|
-
* would put a nostr relay in the latency path of every priced call.
|
|
3544
|
+
* Crediting is pull-based: later requests observe settlement against the durable invoice
|
|
3545
|
+
* binding, so sleeping hosts need no watcher. Reachability is cached and refreshed in the
|
|
3546
|
+
* background for funding menus.
|
|
3863
3547
|
*/
|
|
3864
3548
|
declare class LightningReceive {
|
|
3865
3549
|
private readonly source;
|
|
@@ -3872,36 +3556,21 @@ declare class LightningReceive {
|
|
|
3872
3556
|
private constructor();
|
|
3873
3557
|
private readonly railHealth;
|
|
3874
3558
|
/**
|
|
3875
|
-
*
|
|
3876
|
-
*
|
|
3877
|
-
*
|
|
3878
|
-
*
|
|
3879
|
-
* unreachable.** A wallet outage at boot must not stop a DVM whose other
|
|
3880
|
-
* rails are fine — the menu simply omits `lightning` until a later probe
|
|
3881
|
-
* succeeds. A connection that can spend, or one that can't state what it can
|
|
3882
|
-
* do, is a different thing entirely: it is a standing money risk that no
|
|
3883
|
-
* amount of retrying fixes, so it fails the boot loudly with the fix in the
|
|
3884
|
-
* message.
|
|
3559
|
+
* Return undefined when unconfigured. Permission/URI failures refuse construction with a
|
|
3560
|
+
* corrective message; wallet transport failure constructs an unavailable receive leg so
|
|
3561
|
+
* other rails can still boot. Recovery must validate method scope before advertising
|
|
3562
|
+
* Lightning.
|
|
3885
3563
|
*/
|
|
3886
3564
|
static create(config: LightningReceiveConfig): Promise<LightningReceive | undefined>;
|
|
3887
3565
|
/**
|
|
3888
|
-
*
|
|
3889
|
-
*
|
|
3890
|
-
* Reads the cached observation and never blocks; a stale one kicks off a
|
|
3891
|
-
* background refresh and answers with what we last knew. Advertising a rail
|
|
3892
|
-
* whose wallet is down would hand the caller an option that 402s on use —
|
|
3893
|
-
* the §4 posture is to drop it and let the sale survive on the others.
|
|
3566
|
+
* Answer from cached wallet and optional rail-health observations without blocking. A stale
|
|
3567
|
+
* wallet observation starts a background refresh.
|
|
3894
3568
|
*/
|
|
3895
3569
|
available(): boolean;
|
|
3896
3570
|
/**
|
|
3897
|
-
*
|
|
3898
|
-
*
|
|
3899
|
-
*
|
|
3900
|
-
* signed body — that binding is the internal-review condition-3 commitment on this
|
|
3901
|
-
* rail, and it is why no artifact hash rides the request: there is no
|
|
3902
|
-
* caller-supplied artifact to hash. A re-poll returns the stored row rather
|
|
3903
|
-
* than minting again; a second bolt11 for one `fund_id` would leave two
|
|
3904
|
-
* payable invoices against a funding that can only be credited once.
|
|
3571
|
+
* Return the stored invoice on retry: two payable bolt11s for one fund ID could charge
|
|
3572
|
+
* twice while only one credits. Caller and amount are bound to the recorded credit/funding
|
|
3573
|
+
* pair, without a caller-supplied artifact hash.
|
|
3905
3574
|
*/
|
|
3906
3575
|
issue(ledger: CreditLedgerLike, args: {
|
|
3907
3576
|
creditId: string;
|
|
@@ -3913,24 +3582,9 @@ declare class LightningReceive {
|
|
|
3913
3582
|
description?: string;
|
|
3914
3583
|
}): Promise<CreditInvoiceRecord>;
|
|
3915
3584
|
/**
|
|
3916
|
-
*
|
|
3917
|
-
*
|
|
3918
|
-
*
|
|
3919
|
-
* `blocked` row implies payment — {@link applyOne} returns above the block
|
|
3920
|
-
* classification when the lookup says unpaid — but that is *this fleet's
|
|
3921
|
-
* belief, recorded possibly weeks ago*, and the verb it gates mints balance
|
|
3922
|
-
* against it. One round trip turns the belief into a fact and recovers the
|
|
3923
|
-
* true settlement instant for `settled_at`, which a blocked row never got to
|
|
3924
|
-
* write.
|
|
3925
|
-
*
|
|
3926
|
-
* Unlike {@link settlePending} this **throws**: there is no request whose
|
|
3927
|
-
* latency it protects, and crediting on an unverifiable wallet is exactly
|
|
3928
|
-
* what it exists to prevent. `NOT_FOUND` is the one exception, and it is not
|
|
3929
|
-
* an outage — it is the wallet saying it has never seen this hash, the same
|
|
3930
|
-
* reading {@link applyOne} and the caller-side reconcile in `src/lib/nwc.ts`
|
|
3931
|
-
* take. It comes back as `known: false` because the operator's repair for it
|
|
3932
|
-
* ("you are pointed at a different wallet than the one that minted this")
|
|
3933
|
-
* is not the repair for an unpaid invoice, and certainly not for a retry.
|
|
3585
|
+
* Re-observe the wallet payment before an operator credits a blocked invoice and recover
|
|
3586
|
+
* its settlement instant. Unlike settlePending, lookup failures throw. NOT_FOUND returns
|
|
3587
|
+
* known:false, distinguishing the wrong wallet/unknown hash from an unpaid known invoice.
|
|
3934
3588
|
*/
|
|
3935
3589
|
lookupSettlement(paymentHash: string): Promise<{
|
|
3936
3590
|
settled: boolean;
|
|
@@ -3938,23 +3592,12 @@ declare class LightningReceive {
|
|
|
3938
3592
|
known: boolean;
|
|
3939
3593
|
}>;
|
|
3940
3594
|
/**
|
|
3941
|
-
*
|
|
3942
|
-
*
|
|
3943
|
-
*
|
|
3944
|
-
* The pending rows are read locally first, so the common case (nothing
|
|
3945
|
-
* outstanding) costs one indexed SELECT and no network at all. Bounded to
|
|
3946
|
-
* {@link MAX_SETTLE_CHECKS} invoices on a short deadline, and **never throws
|
|
3947
|
-
* into the request**: a wallet failure here means the caller's balance is
|
|
3948
|
-
* merely not updated yet, which the ordinary `insufficient_credit` path
|
|
3949
|
-
* already states honestly.
|
|
3595
|
+
* Read pending invoices locally, then check at most MAX_SETTLE_CHECKS on bounded wallet
|
|
3596
|
+
* deadlines. Log failures without throwing into the request.
|
|
3950
3597
|
*
|
|
3951
|
-
*
|
|
3952
|
-
*
|
|
3953
|
-
*
|
|
3954
|
-
* the rest of the pass down with it: the next invoice in the window may be
|
|
3955
|
-
* the paid one, and it would never be looked up. Only a transport failure
|
|
3956
|
-
* ends the pass early, because there the wallet itself is gone and the
|
|
3957
|
-
* remaining lookups would just spend the caller's latency confirming it.
|
|
3598
|
+
* An invoice-specific failure must not suppress payable siblings; wallet transport failure
|
|
3599
|
+
* ends the pass. Deterministic refusals retire that row as blocked for operator repair,
|
|
3600
|
+
* while transient failures remain pending.
|
|
3958
3601
|
*/
|
|
3959
3602
|
settlePending(ledger: CreditLedgerLike, args: {
|
|
3960
3603
|
callerPubkey: string;
|
|
@@ -3964,49 +3607,21 @@ declare class LightningReceive {
|
|
|
3964
3607
|
enqueueCreditDeposit?: CreditDepositEnqueue;
|
|
3965
3608
|
nowMs?: number;
|
|
3966
3609
|
}): Promise<InvoiceSettlement[]>;
|
|
3967
|
-
/**
|
|
3968
|
-
* One invoice's settlement check. Returns the applied settlement, or
|
|
3969
|
-
* `undefined` when nothing changed (still unpaid, or retired unpaid).
|
|
3970
|
-
*
|
|
3971
|
-
* Throws whatever the wallet or the ledger threw — classifying that is
|
|
3972
|
-
* {@link retireOrLog}'s job, so this stays one invoice's happy path.
|
|
3973
|
-
*/
|
|
3974
3610
|
private applyOne;
|
|
3975
3611
|
/**
|
|
3976
|
-
*
|
|
3977
|
-
*
|
|
3978
|
-
*
|
|
3979
|
-
* The split that matters is permanent-versus-transient, because it decides
|
|
3980
|
-
* whether the row keeps a slot in the sweep window. A transient failure — the
|
|
3981
|
-
* database blinked, the wallet answered oddly — leaves it `pending` and the
|
|
3982
|
-
* caller's next request retries it. A **permanent** one means the ledger will
|
|
3983
|
-
* refuse this invoice identically forever, so leaving it pending costs a
|
|
3984
|
-
* `lookup_invoice` on every subsequent request and, at
|
|
3985
|
-
* {@link MAX_SETTLE_CHECKS} of them, fills the window so a genuinely payable
|
|
3986
|
-
* invoice behind them is never even looked up. Those get `blocked`, which
|
|
3987
|
-
* drops them out of the sweep and hands the operator the payment hash.
|
|
3612
|
+
* Retire a deterministic uncreditable payment as blocked so it no longer occupies the
|
|
3613
|
+
* bounded sweep window. Transient failures stay pending for a later request; report the
|
|
3614
|
+
* payment hash for operator repair.
|
|
3988
3615
|
*/
|
|
3989
3616
|
private retireOrLog;
|
|
3990
3617
|
/**
|
|
3991
|
-
*
|
|
3992
|
-
*
|
|
3993
|
-
*
|
|
3994
|
-
*
|
|
3995
|
-
* has already committed (a `credit_fundings` entry at this
|
|
3996
|
-
* `(credit_id, fund_id)`, or a `credits` row whose owner, denomination or
|
|
3997
|
-
* rail disagrees with the invoice), or the basis this module rebuilds
|
|
3998
|
-
* identically from the invoice row on every pass. None of them is a race.
|
|
3999
|
-
*
|
|
4000
|
-
* `funding_replayed` is the one that needs a second read to classify.
|
|
4001
|
-
* Usually it *is* benign — a concurrent check won the race and the money is
|
|
4002
|
-
* credited either way — but the key is `(credit_id, fund_id)` and `fund_id`
|
|
4003
|
-
* is **caller-chosen**: reuse it on cashu or x402 after this bolt11 was
|
|
4004
|
-
* minted and the row that collided is a different payment entirely, so the
|
|
4005
|
-
* sats this invoice received have nowhere to land.
|
|
3618
|
+
* Classify funding_replayed by reading the existing funding’s rail. A Lightning row can
|
|
3619
|
+
* represent a concurrent applied check; another rail at this caller-chosen key is a
|
|
3620
|
+
* different payment, leaving the invoice’s sats uncredited. A failed diagnostic read
|
|
3621
|
+
* remains transient, never a guessed permanent refusal.
|
|
4006
3622
|
*/
|
|
4007
3623
|
private blockingReason;
|
|
4008
3624
|
private withBackend;
|
|
4009
|
-
/** Record activity without letting it promote an unvalidated connection. */
|
|
4010
3625
|
private noteOperationOk;
|
|
4011
3626
|
private notePermissionsOk;
|
|
4012
3627
|
private notePermissionRefused;
|
|
@@ -4019,7 +3634,7 @@ declare class LightningReceive {
|
|
|
4019
3634
|
}
|
|
4020
3635
|
|
|
4021
3636
|
/**
|
|
4022
|
-
* Owner display identity surfaced on `/v1/info#owner
|
|
3637
|
+
* Owner display identity surfaced on `/v1/info#owner`. Personal orgs
|
|
4023
3638
|
* resolve to the owner builder's profile; shared orgs resolve to the org's own
|
|
4024
3639
|
* profile. Container DVMs read from env vars; isolate DVMs resolve from Postgres.
|
|
4025
3640
|
*/
|
|
@@ -4029,22 +3644,22 @@ interface OwnerDisplay {
|
|
|
4029
3644
|
avatarUrl?: string | null;
|
|
4030
3645
|
type: "builder" | "org";
|
|
4031
3646
|
}
|
|
4032
|
-
/**
|
|
3647
|
+
/** Builder identity advertised on /v1/info. */
|
|
4033
3648
|
interface BuilderIdentity {
|
|
4034
3649
|
id?: string;
|
|
4035
3650
|
name?: string;
|
|
4036
3651
|
url?: string;
|
|
4037
3652
|
/**
|
|
4038
|
-
* Builder identity x-only secp256k1 pubkey
|
|
3653
|
+
* Builder identity x-only secp256k1 pubkey. When populated, the
|
|
4039
3654
|
* `/v1/info#builder` block also carries `attestation` + `signature` so
|
|
4040
3655
|
* consumers can verify the deploy was signed by the holder of this key.
|
|
4041
3656
|
* Populated at deploy time from `DVMKIT_BUILDER_PUBKEY` (Fly secret); the
|
|
4042
3657
|
* SDK never re-signs at runtime.
|
|
4043
3658
|
*/
|
|
4044
3659
|
pubkey?: string;
|
|
4045
|
-
/** Canonical deploy-time attestation payload
|
|
3660
|
+
/** Canonical deploy-time attestation payload. Served verbatim. */
|
|
4046
3661
|
attestation?: AttestationPayload;
|
|
4047
|
-
/** Schnorr signature over `canonicaliseForSigning(attestation)
|
|
3662
|
+
/** Schnorr signature over `canonicaliseForSigning(attestation)`. */
|
|
4048
3663
|
signature?: string;
|
|
4049
3664
|
}
|
|
4050
3665
|
/** Platform reporter overrides — the host falls back to env when omitted. */
|
|
@@ -4054,28 +3669,29 @@ interface PlatformReporterOpts {
|
|
|
4054
3669
|
/** Platform internal URL. Defaults to `DVMKIT_PLATFORM_URL` env. */
|
|
4055
3670
|
url?: string;
|
|
4056
3671
|
}
|
|
4057
|
-
/** Running host handle returned by serve(). */
|
|
4058
3672
|
interface DVMServeResult {
|
|
4059
3673
|
url: string;
|
|
4060
3674
|
close: () => Promise<void>;
|
|
4061
3675
|
/** Boot snapshot for each mount; configured but unusable rails remain payment-required. */
|
|
4062
3676
|
paymentModes: PaymentMode[];
|
|
4063
3677
|
}
|
|
4064
|
-
/** Options for {@link createDVMHost}. */
|
|
4065
3678
|
interface DVMHostOpts {
|
|
4066
3679
|
/**
|
|
4067
3680
|
* Postgres connection string. Defaults to `DATABASE_URL` env.
|
|
4068
|
-
* Required at runtime
|
|
3681
|
+
* Required at runtime. Boot fails when missing unless `jobStore`
|
|
4069
3682
|
* is explicitly supplied or `devMode` is true (test/dev escape hatches).
|
|
4070
3683
|
*/
|
|
4071
3684
|
database?: string;
|
|
4072
3685
|
/** Listen port. Defaults to `PORT` env or 8080. */
|
|
4073
3686
|
port?: number;
|
|
4074
|
-
/**
|
|
3687
|
+
/**
|
|
3688
|
+
* Environment variables. Defaults to `process.env` when omitted. A supplied
|
|
3689
|
+
* object is the complete host configuration: absent fields do not fall back
|
|
3690
|
+
* to ambient process variables.
|
|
3691
|
+
*/
|
|
4075
3692
|
env?: Record<string, string>;
|
|
4076
|
-
/** Builder identity for `/v1/info` (forward-compatible). */
|
|
4077
3693
|
builder?: BuilderIdentity;
|
|
4078
|
-
/** Owner display identity for `/v1/info#owner
|
|
3694
|
+
/** Owner display identity for `/v1/info#owner`. Falls back to env vars. */
|
|
4079
3695
|
owner?: OwnerDisplay;
|
|
4080
3696
|
/** Platform reporter overrides. Defaults to env-derived values. */
|
|
4081
3697
|
platformReporter?: PlatformReporterOpts;
|
|
@@ -4093,7 +3709,7 @@ interface DVMHostOpts {
|
|
|
4093
3709
|
jobStore?: JobStore;
|
|
4094
3710
|
/**
|
|
4095
3711
|
* Existing Postgres pool to reuse instead of opening one from `database`
|
|
4096
|
-
* (
|
|
3712
|
+
* (also useful as a test seam). When set, the host builds its JobStore / KVStore /
|
|
4097
3713
|
* cashu accumulator on this pool and leaves it open at shutdown — the caller
|
|
4098
3714
|
* owns it. Lets the e2e harness run DVMs in `p2pk-accumulator` mode against
|
|
4099
3715
|
* its single shared pool rather than spawning a pool per DVM.
|
|
@@ -4101,7 +3717,6 @@ interface DVMHostOpts {
|
|
|
4101
3717
|
pool?: Pool;
|
|
4102
3718
|
/** Cashu mints accepted. Defaults to env `DVMKIT_CASHU_MINTS`. */
|
|
4103
3719
|
mints?: string[];
|
|
4104
|
-
/** Cashu receive mode override. */
|
|
4105
3720
|
cashuMode?: CashuMode;
|
|
4106
3721
|
/** MPP handle override. Defaults to env-resolved via `createMppFromOpts`. */
|
|
4107
3722
|
mpp?: MppxServer;
|
|
@@ -4110,7 +3725,7 @@ interface DVMHostOpts {
|
|
|
4110
3725
|
/** Payment methods override. Defaults to derived from configured rails. */
|
|
4111
3726
|
paymentMethods?: PaymentMethod[];
|
|
4112
3727
|
/**
|
|
4113
|
-
* Lightning receive leg for credit funding
|
|
3728
|
+
* Lightning receive leg for credit funding. Defaults to the
|
|
4114
3729
|
* env-resolved `DVMKIT_NWC_RECEIVE_URI` /
|
|
4115
3730
|
* `DVMKIT_NWC_RECEIVE_INVOICE_TTL_SECONDS`. Pass `backend` to inject a
|
|
4116
3731
|
* wallet directly — a test seam that skips the connect-time probe, so
|
|
@@ -4124,9 +3739,7 @@ interface DVMHostOpts {
|
|
|
4124
3739
|
onPaidJobCompleted?: (event: PaidJobCompletion) => void | Promise<void>;
|
|
4125
3740
|
/** When true, payment is skipped only when no payment rail is configured. */
|
|
4126
3741
|
devMode?: boolean;
|
|
4127
|
-
/** Consumed-credential store override (test seam). */
|
|
4128
3742
|
consumedCredentialStore?: ConsumedCredentialStore;
|
|
4129
|
-
/** Mint-health tracker override (test seam). */
|
|
4130
3743
|
mintHealthTracker?: MintHealthTracker;
|
|
4131
3744
|
/** Operator-alert boundary for failed terminal-job retention passes. */
|
|
4132
3745
|
onJobRetentionSweepFailed?: JobManagerOpts["onJobRetentionSweepFailed"];
|
|
@@ -4139,12 +3752,10 @@ interface DVMHostOpts {
|
|
|
4139
3752
|
*/
|
|
4140
3753
|
wrapRouteClientCompatibility?: (sdkGate: ClientCompatibilityGate) => ClientCompatibilityGate;
|
|
4141
3754
|
}
|
|
4142
|
-
/** Per-mount options. Currently only `prefix` is supported. */
|
|
4143
3755
|
interface MountOpts {
|
|
4144
3756
|
/** Path prefix for this DVM's protocol routes (e.g. `/delete-feed`). */
|
|
4145
3757
|
prefix?: string;
|
|
4146
3758
|
}
|
|
4147
|
-
/** Live DVM host. Single wiring locus for the SDK (internal-review). */
|
|
4148
3759
|
interface DVMHost {
|
|
4149
3760
|
/**
|
|
4150
3761
|
* Underlying Hono app for custom non-protocol routes that belong to the
|
|
@@ -4155,15 +3766,15 @@ interface DVMHost {
|
|
|
4155
3766
|
/**
|
|
4156
3767
|
* Resolved Postgres pool — undefined before `host.serve()` completes
|
|
4157
3768
|
* database resolution, set thereafter. Exposed so DVM-local Postgres
|
|
4158
|
-
* consumers (e.g. scrape's `ScrapeDb` per-fetch event store
|
|
3769
|
+
* consumers (e.g. scrape's `ScrapeDb` per-fetch event store)
|
|
4159
3770
|
* can reuse the host's pool rather than constructing their own. Wire
|
|
4160
3771
|
* inside descriptor `onBoot` (fires after pool init, before listener opens).
|
|
4161
3772
|
*/
|
|
4162
3773
|
readonly pool: Pool | undefined;
|
|
4163
3774
|
/**
|
|
4164
|
-
* Money-safe credit ledger
|
|
3775
|
+
* Money-safe credit ledger — undefined before `host.serve()`
|
|
4165
3776
|
* resolves stores, set thereafter. Postgres-backed when a pool exists;
|
|
4166
|
-
* pool-less hosts get a shared in-memory ledger so the
|
|
3777
|
+
* pool-less hosts get a shared in-memory ledger so the fund+draw
|
|
4167
3778
|
* semantics hold everywhere. Same wiring window as `pool` (descriptor
|
|
4168
3779
|
* `onBoot` fires after init, before the listener opens).
|
|
4169
3780
|
*/
|
|
@@ -4173,7 +3784,6 @@ interface DVMHost {
|
|
|
4173
3784
|
* a single mount with no prefix attaches at root.
|
|
4174
3785
|
*/
|
|
4175
3786
|
mount<S, I extends ZodLike | undefined>(descriptor: DVMDescriptor<S, I>, opts?: MountOpts): void;
|
|
4176
|
-
/** Start listening. Returns the live server handle. */
|
|
4177
3787
|
serve(opts?: {
|
|
4178
3788
|
port?: number;
|
|
4179
3789
|
}): Promise<DVMServeResult>;
|
|
@@ -4181,7 +3791,7 @@ interface DVMHost {
|
|
|
4181
3791
|
shutdown(): Promise<void>;
|
|
4182
3792
|
}
|
|
4183
3793
|
/**
|
|
4184
|
-
* Single wiring locus for SDK-side construction
|
|
3794
|
+
* Single wiring locus for SDK-side construction. Replaces both the
|
|
4185
3795
|
* `serve()` wrapper and the `createDVMServer` direct path. Reads env once,
|
|
4186
3796
|
* resolves payment rails, owns the Postgres pool, and mounts each DVM
|
|
4187
3797
|
* descriptor as a Hono sub-app on `host.app`.
|
|
@@ -4190,13 +3800,12 @@ declare function createDVMHost(opts?: DVMHostOpts): DVMHost;
|
|
|
4190
3800
|
|
|
4191
3801
|
/**
|
|
4192
3802
|
* Cached liveness signal for the platform's central Tempo close observer
|
|
4193
|
-
* (internal-review).
|
|
4194
3803
|
*
|
|
4195
3804
|
* A TIP-1034 session channel is only safe to accept while something is
|
|
4196
3805
|
* watching the chain for its close: without that, a caller can consume value,
|
|
4197
3806
|
* request a close, wait out the 900-second grace period and withdraw. So this
|
|
4198
3807
|
* gate is **fail-closed**, which is the opposite of `lightning-rail-health.ts`
|
|
4199
|
-
*
|
|
3808
|
+
* there, withholding a rail over an outage costs a sale; here, advertising
|
|
4200
3809
|
* one costs the money. `tempo/charge` is untouched either way: a charge is an
|
|
4201
3810
|
* ordinary payment with no channel and no close to watch.
|
|
4202
3811
|
*/
|
|
@@ -4218,22 +3827,10 @@ interface TempoObserverHealth {
|
|
|
4218
3827
|
/** Prime the cached signal at boot. */
|
|
4219
3828
|
refresh?(): Promise<void>;
|
|
4220
3829
|
/**
|
|
4221
|
-
*
|
|
4222
|
-
*
|
|
4223
|
-
*
|
|
4224
|
-
* The
|
|
4225
|
-
* close observer's payee-fallback rung 2 — was written once per container
|
|
4226
|
-
* deploy and never refreshed, so a builder flipping sessions on with
|
|
4227
|
-
* `dvmctl env --set` stayed invisible to the fallback indefinitely. That is a
|
|
4228
|
-
* `no_route` on the first close of that DVM's first channel, which is exactly
|
|
4229
|
-
* the race the rung exists to cover.
|
|
4230
|
-
*
|
|
4231
|
-
* The reader must answer from **configuration**, never from
|
|
4232
|
-
* {@link TempoObserverHealth.available} — a fail-closed gate blip must not
|
|
4233
|
-
* de-stamp the DVM, which is the internal-review failure through a new door.
|
|
4234
|
-
*
|
|
4235
|
-
* Returns a disposer the mount calls on shutdown, so a torn-down capability
|
|
4236
|
-
* stops voting in the OR below.
|
|
3830
|
+
* Report the OR of mounted session capabilities to keep the platform's
|
|
3831
|
+
* observer watch signal current. Readers must use configuration, not current
|
|
3832
|
+
* availability: an observer outage must not erase the capability it watches.
|
|
3833
|
+
* The returned disposer removes a mount's contribution on shutdown.
|
|
4237
3834
|
*/
|
|
4238
3835
|
declareSessionCapability?(read: () => boolean): () => void;
|
|
4239
3836
|
}
|
|
@@ -4281,7 +3878,6 @@ interface TempoSettlementReadinessStateStore {
|
|
|
4281
3878
|
clearFailureIfRefilled(version: string, balanceMicro: bigint, lowBalanceMicro: bigint): Promise<boolean>;
|
|
4282
3879
|
clearFailureAfterSuccessfulTransaction(version: string): Promise<boolean>;
|
|
4283
3880
|
}
|
|
4284
|
-
/** Construction inputs for {@link TempoSettlementReadiness}. */
|
|
4285
3881
|
interface TempoSettlementReadinessOptions {
|
|
4286
3882
|
address: `0x${string}`;
|
|
4287
3883
|
network: string;
|
|
@@ -4328,7 +3924,6 @@ declare class TempoSettlementReadiness {
|
|
|
4328
3924
|
start(): void;
|
|
4329
3925
|
/** Stop the cadence without affecting any in-flight reading. */
|
|
4330
3926
|
stop(): void;
|
|
4331
|
-
/** Read and report the configured fee-token balance now. */
|
|
4332
3927
|
checkBalance(opts?: {
|
|
4333
3928
|
successfulTransaction?: boolean;
|
|
4334
3929
|
recoveryVersion?: string;
|
|
@@ -4351,24 +3946,11 @@ declare class TempoSettlementReadiness {
|
|
|
4351
3946
|
private notify;
|
|
4352
3947
|
}
|
|
4353
3948
|
|
|
4354
|
-
/**
|
|
4355
|
-
* Options for the revenue reporter readiness assertion (internal-review).
|
|
4356
|
-
*
|
|
4357
|
-
* The check branches on `platformUrl` presence — that's the unambiguous
|
|
4358
|
-
* "platform-hosted" signal. `failFast` stays on the opts as a general SDK
|
|
4359
|
-
* strict-mode flag but is not consulted by this assertion.
|
|
4360
|
-
*/
|
|
4361
3949
|
interface RevenueBootCheckOpts {
|
|
4362
3950
|
/**
|
|
4363
|
-
*
|
|
4364
|
-
* boundary (internal-review). Reserved for future warning-vs-fatal SDK checks; the
|
|
4365
|
-
* reporter check itself ignores it (internal-review), hence optional.
|
|
3951
|
+
* Reporter readiness ignores this host strict-mode flag.
|
|
4366
3952
|
*/
|
|
4367
3953
|
failFast?: boolean;
|
|
4368
|
-
/**
|
|
4369
|
-
* The SDK's canonical "this is a dev/test run" signal. Exempts the boot from
|
|
4370
|
-
* the platform-hosted wiring requirement (internal-review) — see below.
|
|
4371
|
-
*/
|
|
4372
3954
|
devMode?: boolean;
|
|
4373
3955
|
platformToken: string | undefined;
|
|
4374
3956
|
platformUrl: string | undefined;
|
|
@@ -4376,63 +3958,38 @@ interface RevenueBootCheckOpts {
|
|
|
4376
3958
|
hasPgPool: boolean;
|
|
4377
3959
|
}
|
|
4378
3960
|
/**
|
|
4379
|
-
*
|
|
4380
|
-
*
|
|
4381
|
-
*
|
|
4382
|
-
*
|
|
4383
|
-
* Partial wiring is always fatal — there's no legitimate "platform set my URL
|
|
4384
|
-
* but didn't finish wiring" case, and `DVMKIT_FAIL_FAST` has no effect here.
|
|
4385
|
-
*
|
|
4386
|
-
* Self-hosted DVMs (no `DVMKIT_PLATFORM_URL`) have no platform reporter
|
|
4387
|
-
* contract to enforce — the SDK skips validation entirely.
|
|
4388
|
-
*
|
|
4389
|
-
* `devMode` boots are exempt (internal-review), mirroring the test-mint guard's
|
|
4390
|
-
* exemption in `createDVMHost` (internal-review, `host.ts`). `devMode` is set by
|
|
4391
|
-
* `dvmctl dev`, `standalone development tooling`, the money-loop, and the e2e/smoke
|
|
4392
|
-
* harnesses — all of which spread `...process.env`, so a builder whose shell
|
|
4393
|
-
* exports `DVMKIT_PLATFORM_URL` for their `dvmctl` platform commands would
|
|
4394
|
-
* otherwise hit this FATAL on a dev server that reports no revenue anyway (no
|
|
4395
|
-
* payment, no auth, no DB). Production boots never set it: first-party
|
|
4396
|
-
* container entrypoints and `dvmctl serve` (`buildServeOpts`) both leave it
|
|
4397
|
-
* unset — asserted in
|
|
4398
|
-
* `__tests__/revenue-reporter-boot.test.ts`, not just claimed here.
|
|
3961
|
+
* Hosted boots (a platform URL is set) require a token, DVM ID and database;
|
|
3962
|
+
* partial wiring is fatal regardless of `failFast`. Self-hosted and `devMode`
|
|
3963
|
+
* boots skip this assertion. The exemption does not disable an already wired
|
|
3964
|
+
* reporter; the banner separately describes whether reporting is active.
|
|
4399
3965
|
*/
|
|
4400
3966
|
declare function assertRevenueReporterReady(opts: RevenueBootCheckOpts): void;
|
|
4401
3967
|
/** What the host's boot banner knows about the revenue reporter's wiring. */
|
|
4402
3968
|
interface ReporterBannerOpts {
|
|
4403
3969
|
/** `DVMKIT_PLATFORM_URL` — the platform-hosted signal. */
|
|
4404
3970
|
platformUrl: string | undefined;
|
|
4405
|
-
/** Whether `DVMKIT_PLATFORM_TOKEN` resolved — never the token itself
|
|
3971
|
+
/** Whether `DVMKIT_PLATFORM_TOKEN` resolved — never the token itself. */
|
|
4406
3972
|
hasPlatformToken: boolean;
|
|
4407
3973
|
/** Whether `DVMKIT_DVM_ID` resolved. */
|
|
4408
3974
|
hasDvmId: boolean;
|
|
4409
|
-
/** Whether a Postgres pool is wired. */
|
|
4410
3975
|
databaseConnected: boolean;
|
|
4411
|
-
/** Dev/test boot — exempt from the wiring assertion above (internal-review). */
|
|
4412
3976
|
devMode: boolean;
|
|
4413
3977
|
}
|
|
4414
3978
|
/**
|
|
4415
|
-
* Render the boot banner's `Platform rep.` line
|
|
3979
|
+
* Render the boot banner's `Platform rep.` line.
|
|
4416
3980
|
*
|
|
4417
3981
|
* Lives here, next to the assertion, because `active` has to mirror the
|
|
4418
3982
|
* reporter's own wiring gate in `createDVMServer` below — token **and** dvmId
|
|
4419
3983
|
* **and** database. Miss one and the banner claims a reporter that was never
|
|
4420
3984
|
* constructed.
|
|
4421
3985
|
*
|
|
4422
|
-
*
|
|
4423
|
-
*
|
|
4424
|
-
* assertion (internal-review), making it the one path where partial wiring reaches
|
|
4425
|
-
* this line — say so plainly rather than shouting `misconfigured` at a builder
|
|
4426
|
-
* whose shell merely exports the platform URL.
|
|
3986
|
+
* Partial dev wiring reaches the banner because the boot assertion is exempt;
|
|
3987
|
+
* it reports a dev status rather than a production configuration failure.
|
|
4427
3988
|
*/
|
|
4428
3989
|
declare function revenueReporterBannerState(s: ReporterBannerOpts): string;
|
|
4429
|
-
/** Options for creating the SDK server app. */
|
|
4430
3990
|
interface SDKServerOpts {
|
|
4431
|
-
/** Environment variables for ctx.env. */
|
|
4432
3991
|
env: Record<string, string>;
|
|
4433
|
-
/** KV store for ctx.store. */
|
|
4434
3992
|
store: KVStore;
|
|
4435
|
-
/** Cashu mints accepted for payment. */
|
|
4436
3993
|
mints?: string[];
|
|
4437
3994
|
/** Persistent backing store for job records. Default: in-memory. */
|
|
4438
3995
|
jobStore?: JobStore;
|
|
@@ -4440,7 +3997,6 @@ interface SDKServerOpts {
|
|
|
4440
3997
|
devMode?: boolean;
|
|
4441
3998
|
/** Payment methods this DVM accepts. Default: derived from configured rails — adds "cashu" when `mints` is set, "x402" when `x402` is set, "tempo" when `tempo` is set. */
|
|
4442
3999
|
paymentMethods?: PaymentMethod[];
|
|
4443
|
-
/** x402 stablecoin payment configuration. */
|
|
4444
4000
|
x402?: X402Config;
|
|
4445
4001
|
/** Live facilitator capability gate. Hosts wire this after probing `/supported`. */
|
|
4446
4002
|
x402FacilitatorHealth?: X402FacilitatorHealthLike;
|
|
@@ -4449,13 +4005,13 @@ interface SDKServerOpts {
|
|
|
4449
4005
|
/**
|
|
4450
4006
|
* Tracks already-consumed upfront-flow MPP credentials so the same
|
|
4451
4007
|
* credential can't satisfy `POST /v1/job` twice within its `expires`
|
|
4452
|
-
* window
|
|
4008
|
+
* window. Default: in-memory, bounded, per-process. Swap for a
|
|
4453
4009
|
* shared backend before deploying multiple replicas.
|
|
4454
4010
|
*/
|
|
4455
4011
|
consumedCredentialStore?: ConsumedCredentialStore;
|
|
4456
4012
|
/**
|
|
4457
4013
|
* Tracks already-consumed admin-endpoint nonces so a signed P2PK challenge
|
|
4458
|
-
* can't be replayed within the 5-minute sliding window
|
|
4014
|
+
* can't be replayed within the 5-minute sliding window. Default:
|
|
4459
4015
|
* in-memory, bounded, per-process. Swap for a shared backend before
|
|
4460
4016
|
* deploying multiple replicas.
|
|
4461
4017
|
*/
|
|
@@ -4469,14 +4025,14 @@ interface SDKServerOpts {
|
|
|
4469
4025
|
* registration order.
|
|
4470
4026
|
*/
|
|
4471
4027
|
healthHandler?: (c: Context<AppEnv>) => Response | Promise<Response>;
|
|
4472
|
-
/** Cashu receive mode
|
|
4028
|
+
/** Cashu receive mode. */
|
|
4473
4029
|
cashuMode?: CashuMode;
|
|
4474
4030
|
/**
|
|
4475
|
-
* Builder's NUT-11 P2PK lock pubkey
|
|
4031
|
+
* Builder's NUT-11 P2PK lock pubkey. Used as the seed pubkey for
|
|
4476
4032
|
* `seedLockPubkeyState` on first boot, as the admin-auth identity, and as
|
|
4477
4033
|
* part of the gate for whether `/v1/info` advertises `cashu`. After first
|
|
4478
4034
|
* boot, the authoritative `[current, ...retired]` state lives in the DVM's
|
|
4479
|
-
* Postgres via `dvm_lock_pubkeys`; rotations
|
|
4035
|
+
* Postgres via `dvm_lock_pubkeys`; rotations mutate that store
|
|
4480
4036
|
* directly.
|
|
4481
4037
|
*/
|
|
4482
4038
|
lockPubkey?: string;
|
|
@@ -4485,12 +4041,11 @@ interface SDKServerOpts {
|
|
|
4485
4041
|
/**
|
|
4486
4042
|
* Rotation grace window (seconds) — retired pubkeys remain advertised on
|
|
4487
4043
|
* `cashu.lock_pubkeys` and accepted by the receive path until they age out.
|
|
4488
|
-
*
|
|
4489
|
-
* `BUILDER_LOCK_PUBKEY_GRACE_SECONDS` env (internal-review).
|
|
4044
|
+
* Defaults to 24h.
|
|
4490
4045
|
*/
|
|
4491
4046
|
lockPubkeyGraceSeconds?: number;
|
|
4492
4047
|
/**
|
|
4493
|
-
* Per-DVM canonical identifier
|
|
4048
|
+
* Per-DVM canonical identifier. Advertised on `/v1/info` as
|
|
4494
4049
|
* `cashu.canonical_id` so agents can derive the matching per-DVM refund
|
|
4495
4050
|
* subkey at `m/1789'/2'/hashed(canonical_dvm_id)'`. Defaults to `dvmId`
|
|
4496
4051
|
* (the platform's DVM UUID) when unset — keeps `/v1/info` self-consistent
|
|
@@ -4500,30 +4055,23 @@ interface SDKServerOpts {
|
|
|
4500
4055
|
/** Postgres pool for builder admin state and wallet accumulator persistence. */
|
|
4501
4056
|
db?: Pool;
|
|
4502
4057
|
/**
|
|
4503
|
-
*
|
|
4504
|
-
*
|
|
4505
|
-
* revenue-reporter boot check is gated on platform context, not on this
|
|
4506
|
-
* flag (internal-review).
|
|
4058
|
+
* Host strict-mode flag. Reporter readiness follows hosted context regardless
|
|
4059
|
+
* of this value.
|
|
4507
4060
|
*/
|
|
4508
4061
|
failFast?: boolean;
|
|
4509
|
-
/** Agent-wallet
|
|
4062
|
+
/** Agent-wallet: per-DVM identifier — drives the monitor advisory lock + replay key. */
|
|
4510
4063
|
dvmId?: string;
|
|
4511
4064
|
/**
|
|
4512
|
-
* Runtime mint-health tracker
|
|
4513
|
-
* fresh one whenever `mints?.length > 0`.
|
|
4514
|
-
* `/v1/info
|
|
4515
|
-
*
|
|
4065
|
+
* Runtime mint-health tracker. When omitted, the server builds a
|
|
4066
|
+
* fresh one whenever `mints?.length > 0`. Healthy mints are preferred in
|
|
4067
|
+
* `/v1/info`; when none are healthy, transiently sick mints remain advertised.
|
|
4068
|
+
* NUT-incompatible mints are excluded even from that fallback.
|
|
4516
4069
|
*/
|
|
4517
4070
|
mintHealthTracker?: MintHealthTracker;
|
|
4518
|
-
/**
|
|
4519
|
-
* Shared fx fetcher used to translate USD descriptor prices and fiat-form
|
|
4520
|
-
* `ctx.requestPayment` calls into sats (internal-review). When omitted, the server
|
|
4521
|
-
* builds one from `env.DVMKIT_FX_SOURCE` (or the bundled CoinGecko default).
|
|
4522
|
-
* Tests pass a deterministic stub.
|
|
4523
|
-
*/
|
|
4071
|
+
/** Shared upfront/mid-job fiat conversion fetcher. Defaults to `DVMKIT_FX_SOURCE` or CoinGecko. */
|
|
4524
4072
|
fxFetcher?: FxFetcher;
|
|
4525
4073
|
/**
|
|
4526
|
-
* Platform API token for revenue reporting
|
|
4074
|
+
* Platform API token for revenue reporting. Read from
|
|
4527
4075
|
* `opts.env.DVMKIT_PLATFORM_TOKEN` when not set explicitly. When both
|
|
4528
4076
|
* `platformToken` and `platformUrl` are present alongside `db` and `dvmId`,
|
|
4529
4077
|
* the SDK auto-constructs a `RevenueReporter` and wires it into the
|
|
@@ -4531,35 +4079,32 @@ interface SDKServerOpts {
|
|
|
4531
4079
|
*/
|
|
4532
4080
|
platformToken?: string;
|
|
4533
4081
|
/**
|
|
4534
|
-
* Platform API URL for revenue reporting
|
|
4082
|
+
* Platform API URL for revenue reporting. Read from
|
|
4535
4083
|
* `opts.env.DVMKIT_PLATFORM_URL` when not set explicitly.
|
|
4536
4084
|
*/
|
|
4537
4085
|
platformUrl?: string;
|
|
4538
|
-
/**
|
|
4539
|
-
* Advanced override: callback fired when a paid job completes (internal-review).
|
|
4540
|
-
* When set, bypasses the SDK's automatic `RevenueReporter` wiring — the
|
|
4541
|
-
* caller assumes full responsibility for revenue tracking. Intended for
|
|
4542
|
-
* tests and advanced integrations only.
|
|
4543
|
-
*/
|
|
4086
|
+
/** Observes completed jobs with a positive settled draw; failures are logged and swallowed. */
|
|
4544
4087
|
onPaidJobCompleted?: (event: PaidJobCompletion) => void | Promise<void>;
|
|
4545
4088
|
paymentMode?: PaymentMode;
|
|
4089
|
+
/** Overrides automatic RevenueReporter wiring; the caller owns revenue delivery. */
|
|
4546
4090
|
onJobCompleted?: JobManagerOpts["onJobCompleted"];
|
|
4547
4091
|
/**
|
|
4548
4092
|
* Advanced override for a declared cost on a terminal job that emitted no
|
|
4549
|
-
* revenue report
|
|
4093
|
+
* revenue report. Auto-wired to the reporter's durable
|
|
4550
4094
|
* job-cost path; intended for tests and advanced integrations only.
|
|
4551
4095
|
*/
|
|
4552
4096
|
onJobCost?: JobManagerOpts["onJobCost"];
|
|
4097
|
+
onJobTerminal?: JobManagerOpts["onJobTerminal"];
|
|
4553
4098
|
/**
|
|
4554
4099
|
* Advanced override: callback fired when the stale-job reaper force-fails a
|
|
4555
|
-
* *paid* job
|
|
4100
|
+
* *paid* job. Auto-wired to the `RevenueReporter`'s paid-job-death
|
|
4556
4101
|
* report alongside `onJobCompleted`; set explicitly only for tests / advanced
|
|
4557
4102
|
* integrations that own their own reporting.
|
|
4558
4103
|
*/
|
|
4559
4104
|
onPaidJobDeath?: JobManagerOpts["onPaidJobDeath"];
|
|
4560
4105
|
/**
|
|
4561
4106
|
* Advanced override: callback fired when a settled credit draw could not
|
|
4562
|
-
* book revenue because its rail was unavailable
|
|
4107
|
+
* book revenue because its rail was unavailable. Auto-wired to
|
|
4563
4108
|
* the platform reporter alongside the completion and paid-job-death paths.
|
|
4564
4109
|
*/
|
|
4565
4110
|
onRevenueSkippedNoRail?: (info: RevenueSkippedNoRailPayload) => void | Promise<void>;
|
|
@@ -4568,22 +4113,22 @@ interface SDKServerOpts {
|
|
|
4568
4113
|
/** Advanced clear-on-recovery callback for clean job-retention passes. */
|
|
4569
4114
|
onJobRetentionSweepRecovered?: JobManagerOpts["onJobRetentionSweepRecovered"];
|
|
4570
4115
|
/**
|
|
4571
|
-
* Advanced override: callback fired when a payment funds a credit
|
|
4116
|
+
* Advanced override: callback fired when a payment funds a credit.
|
|
4572
4117
|
* Auto-wired to the `RevenueReporter`'s deposit report alongside
|
|
4573
4118
|
* `onJobCompleted`; set explicitly only for tests / advanced integrations
|
|
4574
4119
|
* that own their own reporting. Fundings are deposits — a liability until
|
|
4575
|
-
* drawn — reported separately from the draws that become revenue (
|
|
4120
|
+
* drawn — reported separately from the draws that become revenue (the deposit-accounting rule).
|
|
4576
4121
|
*/
|
|
4577
4122
|
onCreditFunded?: (info: CreditDepositPayload) => void | Promise<void>;
|
|
4578
4123
|
/**
|
|
4579
4124
|
* Grace window (ms) a reactivating machine waits for a live original handler
|
|
4580
4125
|
* to win the single-execution claim before replaying an `awaiting-input` job
|
|
4581
|
-
*
|
|
4126
|
+
* Defaults to the JobManager's built-in value; exposed as an ops
|
|
4582
4127
|
* override and a test seam.
|
|
4583
4128
|
*/
|
|
4584
4129
|
reactivationClaimGraceMs?: JobManagerOpts["reactivationClaimGraceMs"];
|
|
4585
4130
|
/**
|
|
4586
|
-
* Builder identity for `/v1/info#builder
|
|
4131
|
+
* Builder identity for `/v1/info#builder`. Inline alternative
|
|
4587
4132
|
* to the `DVMKIT_BUILDER_PUBKEY` / `_ATTESTATION` / `_SIGNATURE` env vars
|
|
4588
4133
|
* the platform's deploy pipeline injects as Fly secrets. When set,
|
|
4589
4134
|
* `builder.{pubkey, attestation, signature}` take per-field precedence
|
|
@@ -4591,10 +4136,10 @@ interface SDKServerOpts {
|
|
|
4591
4136
|
* `/v1/info#builder` block to be emitted.
|
|
4592
4137
|
*/
|
|
4593
4138
|
builder?: BuilderIdentity;
|
|
4594
|
-
/** Owner display identity for `/v1/info#owner
|
|
4139
|
+
/** Owner display identity for `/v1/info#owner`. Falls back to env vars. */
|
|
4595
4140
|
owner?: OwnerDisplay;
|
|
4596
4141
|
/**
|
|
4597
|
-
* Per-DVM receipt-signing secret, 32 bytes hex
|
|
4142
|
+
* Per-DVM receipt-signing secret, 32 bytes hex. Resolved at the
|
|
4598
4143
|
* host boundary from `DVMKIT_RECEIPT_KEY` — the deploy pipeline provisions
|
|
4599
4144
|
* it as a Fly secret, derived one-way from the builder identity key and
|
|
4600
4145
|
* bound into the deploy attestation's `receipt_pubkey`.
|
|
@@ -4605,25 +4150,25 @@ interface SDKServerOpts {
|
|
|
4605
4150
|
*/
|
|
4606
4151
|
receiptKey?: string;
|
|
4607
4152
|
/**
|
|
4608
|
-
* Credit ledger every payment funds and draws through
|
|
4153
|
+
* Credit ledger every payment funds and draws through. The host
|
|
4609
4154
|
* passes the Postgres `CreditLedger` when a pool exists; standalone /
|
|
4610
4155
|
* pool-less setups default to a per-server `MemoryCreditLedger` so the
|
|
4611
4156
|
* fund+draw semantics (and receipts' credit block) hold everywhere.
|
|
4612
4157
|
*/
|
|
4613
4158
|
creditLedger?: CreditLedgerLike;
|
|
4614
4159
|
/**
|
|
4615
|
-
* Durable x402/mpp processed-payment markers
|
|
4160
|
+
* Durable x402/mpp processed-payment markers. Defaults to the
|
|
4616
4161
|
* in-memory store; the host passes the Postgres one alongside the ledger.
|
|
4617
4162
|
*/
|
|
4618
4163
|
processedPayments?: ProcessedPaymentStore;
|
|
4619
4164
|
/**
|
|
4620
|
-
* Lightning credit-funding receive leg
|
|
4165
|
+
* Lightning credit-funding receive leg, when the host resolved a
|
|
4621
4166
|
* receive-only NWC connection. Absent ⇒ `lightning` never appears on the
|
|
4622
4167
|
* funding menu and `POST /v1/credit` refuses the method.
|
|
4623
4168
|
*/
|
|
4624
4169
|
lightningReceive?: LightningReceive;
|
|
4625
4170
|
/**
|
|
4626
|
-
* Liveness of the platform's Tempo close observer
|
|
4171
|
+
* Liveness of the platform's Tempo close observer. Absent ⇒ this
|
|
4627
4172
|
* DVM is self-hosted and answers for its own close watching, so nothing is
|
|
4628
4173
|
* withheld. Present and unhealthy ⇒ `tempo/session` is withheld while
|
|
4629
4174
|
* `tempo/charge` stays available.
|
|
@@ -4632,35 +4177,25 @@ interface SDKServerOpts {
|
|
|
4632
4177
|
/** Platform-hosted operator fee-token readiness; absent outside hosted sessions. */
|
|
4633
4178
|
tempoSettlementReadiness?: TempoSettlementReadiness;
|
|
4634
4179
|
/**
|
|
4635
|
-
* Tuning for the credit-expiry sweep
|
|
4180
|
+
* Tuning for the credit-expiry sweep. Omitted, the sweep runs on
|
|
4636
4181
|
* its own defaults whenever the ledger supports it; tests pass `intervalMs: 0`
|
|
4637
4182
|
* and `bootDelayMs: 0` to hold the clock still and drive `runOnce` by hand.
|
|
4638
4183
|
*/
|
|
4639
4184
|
creditExpirySweep?: CreditExpirySweepOpts;
|
|
4640
4185
|
}
|
|
4641
|
-
/**
|
|
4642
|
-
* Per-descriptor server construction — internal to the SDK after host
|
|
4643
|
-
* consolidation (internal-review). Public callers use `createDVMHost` instead; this
|
|
4644
|
-
* function is what the host invokes per `host.mount(dvm)`.
|
|
4645
|
-
*
|
|
4646
|
-
* Builds a Hono sub-app for one DVM descriptor, wires payment verification,
|
|
4647
|
-
* auto-wires `RevenueReporter` when platform env + db + dvmId are present, and
|
|
4648
|
-
* runs the platform-hosted partial-wiring assertion (internal-review) — gated on
|
|
4649
|
-
* `DVMKIT_PLATFORM_URL` presence, independent of `DVMKIT_FAIL_FAST`.
|
|
4650
|
-
*/
|
|
4651
4186
|
declare function createDVMServer<State, InputSchema extends ZodLike | undefined>(descriptor: DVMDescriptor<State, InputSchema>, opts: SDKServerOpts): Promise<{
|
|
4652
4187
|
app: Hono<AppEnv>;
|
|
4653
4188
|
authAudience?: SignedRequestAudience;
|
|
4654
4189
|
shutdown: () => Promise<void>;
|
|
4655
4190
|
jobManager: JobManager<State, InputSchema>;
|
|
4656
4191
|
revenueReporterActive: boolean;
|
|
4657
|
-
/** The payout reporter
|
|
4192
|
+
/** The payout reporter, for the host to attach its Tempo readers to. */
|
|
4658
4193
|
payoutReporter?: PayoutReporter;
|
|
4659
4194
|
}>;
|
|
4660
4195
|
/**
|
|
4661
4196
|
* Hono env for the SDK's DVM app. `unknownRoute` is set by
|
|
4662
4197
|
* {@link unknownRouteNotFound} so the `dvm.request` span can classify a 404 as
|
|
4663
|
-
* scanner-probe noise vs a registered route's own 404
|
|
4198
|
+
* scanner-probe noise vs a registered route's own 404.
|
|
4664
4199
|
*
|
|
4665
4200
|
* Exported as a type only, for route modules split out of this file
|
|
4666
4201
|
* (`credit-routes.ts`) — a type-only import erases at compile time, so it
|
|
@@ -4676,7 +4211,7 @@ interface AppEnv {
|
|
|
4676
4211
|
/**
|
|
4677
4212
|
* `notFound` handler for paths that match no registered route — scanner probes
|
|
4678
4213
|
* (`GET /robots.txt`, `/.env`, …). Tags the request context so the `dvm.request`
|
|
4679
|
-
* span downgrades the 404 to `warn`-level probe noise
|
|
4214
|
+
* span downgrades the 404 to `warn`-level probe noise instead of
|
|
4680
4215
|
* crowding out real errors in a `--level error` sweep, and returns a
|
|
4681
4216
|
* machine-parseable JSON 404 (Hono's default is plain text). Registered on both
|
|
4682
4217
|
* the DVM app (standalone `createDVMServer`) and the host app (mounted path):
|
|
@@ -4689,13 +4224,8 @@ interface AppEnv {
|
|
|
4689
4224
|
declare function unknownRouteNotFound(c: Context): Response;
|
|
4690
4225
|
|
|
4691
4226
|
/**
|
|
4692
|
-
*
|
|
4693
|
-
*
|
|
4694
|
-
*
|
|
4695
|
-
* This type deliberately excludes the per-caller echo. `/v1/info` is public
|
|
4696
|
-
* and cacheable, so its type must make a caller credit ID or balance
|
|
4697
|
-
* unrepresentable rather than relying on the current handler not to ask for
|
|
4698
|
-
* one.
|
|
4227
|
+
* Public, cacheable prepaid terms exclude caller IDs and balances. toCreditTerms explicitly
|
|
4228
|
+
* whitelists this shape so later caller-specific menu fields cannot leak into /v1/info.
|
|
4699
4229
|
*/
|
|
4700
4230
|
interface CreditTerms {
|
|
4701
4231
|
/** Smallest funding accepted, in 1e-6 units of `currency`. */
|
|
@@ -4708,7 +4238,10 @@ interface CreditTerms {
|
|
|
4708
4238
|
currency: string;
|
|
4709
4239
|
/** Rails a top-up may be funded over. */
|
|
4710
4240
|
funding: FundingMethod[];
|
|
4711
|
-
/**
|
|
4241
|
+
/**
|
|
4242
|
+
* Tempo methods are actionable. withheld names configured but temporarily unavailable
|
|
4243
|
+
* instruments, preserving capability discovery without advertising them for funding.
|
|
4244
|
+
*/
|
|
4712
4245
|
tempo?: {
|
|
4713
4246
|
methods: {
|
|
4714
4247
|
method: string;
|
|
@@ -4720,63 +4253,32 @@ interface CreditTerms {
|
|
|
4720
4253
|
reason: string;
|
|
4721
4254
|
}[];
|
|
4722
4255
|
};
|
|
4723
|
-
/**
|
|
4256
|
+
/**
|
|
4257
|
+
* Present with offered x402 funding. Reusable batch settlement is enabled by default; exact
|
|
4258
|
+
* requires explicit acceptance of its manual refund obligation.
|
|
4259
|
+
*/
|
|
4724
4260
|
x402?: {
|
|
4725
4261
|
schemes: {
|
|
4726
4262
|
scheme: string;
|
|
4727
4263
|
network: string;
|
|
4728
4264
|
}[];
|
|
4729
4265
|
};
|
|
4730
|
-
/**
|
|
4266
|
+
/**
|
|
4267
|
+
* Configured Lightning receive floor in micro-units of currency, including the advertised
|
|
4268
|
+
* FX margin. Issuance checks the raw sats floor at current rates; arbitrary drift can still
|
|
4269
|
+
* make the advertised amount insufficient.
|
|
4270
|
+
*/
|
|
4731
4271
|
lightning_min_micro?: number;
|
|
4732
4272
|
}
|
|
4733
4273
|
/**
|
|
4734
|
-
*
|
|
4735
|
-
*
|
|
4736
|
-
*
|
|
4737
|
-
* `min_micro` / `max_micro` / `ttl_ms` are the DVM's sizing terms — `max_micro`
|
|
4738
|
-
* binds the **residual balance**, not a funding amount, so a job priced above
|
|
4739
|
-
* it still clears as fund-and-immediately-draw. `funding` is the rail list a
|
|
4740
|
-
* top-up may arrive on — a {@link FundingMethod}, not a `PaymentMethod`, since
|
|
4741
|
-
* `lightning` funds a credit without ever being an attached-proof wire method
|
|
4742
|
-
* (internal-review). It degrades per spec §4: a rail whose backing wallet is
|
|
4743
|
-
* unreachable drops out and the sale survives on the others.
|
|
4274
|
+
* Funding options and sizing in the wire denomination. max_micro limits residual balance,
|
|
4275
|
+
* not a job’s price; fund-and-draw can spend a larger payment immediately. Lightning funds
|
|
4276
|
+
* credit but is not an attached-proof payment method.
|
|
4744
4277
|
*
|
|
4745
|
-
*
|
|
4746
|
-
*
|
|
4747
|
-
* so one round trip answers "what does this cost, and what do I have left?"
|
|
4748
|
-
* (spec §7).
|
|
4278
|
+
* An authenticated caller’s live credit may add an echo with funded and spendable balance;
|
|
4279
|
+
* pending holds are included only in the funded figure.
|
|
4749
4280
|
*/
|
|
4750
4281
|
interface CreditMenu extends CreditTerms {
|
|
4751
|
-
/**
|
|
4752
|
-
* MPP instruments accepted for this credit, reusable sessions first when
|
|
4753
|
-
* available.
|
|
4754
|
-
*
|
|
4755
|
-
* `withheld` names an instrument this DVM is configured for and is not
|
|
4756
|
-
* offering at this instant, with the reason (internal-review). Two audiences read
|
|
4757
|
-
* it. An agent gets to relay *why* `tempo/session` vanished instead of
|
|
4758
|
-
* inferring the DVM never had it. And the platform's deploy probe reads
|
|
4759
|
-
* capability from `methods ∪ withheld`, so a deploy landing during an
|
|
4760
|
-
* observer blip no longer stamps `tempo_session_advertised_at` NULL and
|
|
4761
|
-
* drops the DVM out of the close observer's payee fallback permanently.
|
|
4762
|
-
* `methods` stays the list a caller acts on — never widen it to include a
|
|
4763
|
-
* withheld instrument.
|
|
4764
|
-
*/
|
|
4765
|
-
/**
|
|
4766
|
-
* Which x402 flavours a top-up may arrive on, and the chain each rides
|
|
4767
|
-
* (internal-review). Present exactly when `funding` lists `x402`. Reusable
|
|
4768
|
-
* `batch-settlement` is admitted by default; one-payment `exact` appears
|
|
4769
|
-
* only when the builder explicitly accepts its manual refund obligation.
|
|
4770
|
-
*/
|
|
4771
|
-
/**
|
|
4772
|
-
* Smallest funding the `lightning` rail accepts, in 1e-6 units of
|
|
4773
|
-
* `currency`. Present only when `funding` lists `lightning`. This is the
|
|
4774
|
-
* deployment's sats-physical receive floor (internal-review) rendered into fiat
|
|
4775
|
-
* with a ×{@link LIGHTNING_FLOOR_FX_MARGIN} margin absorbing rate drift
|
|
4776
|
-
* between menu fetch and funding — issuance enforces the *raw* floor at
|
|
4777
|
-
* request-time rates, so funding exactly this figure always clears. Rails
|
|
4778
|
-
* without an entry (cashu) have no floor beyond `min_micro`.
|
|
4779
|
-
*/
|
|
4780
4282
|
/** The caller's credit this echo describes. Present only with the echo. */
|
|
4781
4283
|
credit_id?: string;
|
|
4782
4284
|
/** Funded balance, pending holds NOT subtracted. */
|
|
@@ -4786,7 +4288,6 @@ interface CreditMenu extends CreditTerms {
|
|
|
4786
4288
|
/** Absolute expiry of the echoed credit, ms since epoch. */
|
|
4787
4289
|
expiry_ms?: number;
|
|
4788
4290
|
}
|
|
4789
|
-
/** Everything {@link buildCreditMenu} needs, assembled per request. */
|
|
4790
4291
|
interface BuildCreditMenuArgs {
|
|
4791
4292
|
/**
|
|
4792
4293
|
* Which runtime is answering. `"isolate"` is refused unconditionally — see
|
|
@@ -4796,13 +4297,8 @@ interface BuildCreditMenuArgs {
|
|
|
4796
4297
|
/** The DVM's resolved `credit` block; `undefined` means it didn't opt in. */
|
|
4797
4298
|
config: ResolvedCreditConfig | undefined;
|
|
4798
4299
|
/**
|
|
4799
|
-
*
|
|
4800
|
-
*
|
|
4801
|
-
* `DVMServer.creditCurrency()` (internal-review). Deliberately *not* the currency of
|
|
4802
|
-
* the response this menu rides on: a quote priced elsewhere is refused before
|
|
4803
|
-
* it reaches here, so the two agree, and sourcing it from the response is
|
|
4804
|
-
* what left `POST /v1/credit` — which has no response to read — funding in
|
|
4805
|
-
* hardwired USD at a DVM whose jobs draw in something else.
|
|
4300
|
+
* Use the DVM’s declared denomination, including /v1/credit where no quote response supplies
|
|
4301
|
+
* it. Quote validation refuses a different denomination before menu construction.
|
|
4806
4302
|
*/
|
|
4807
4303
|
currency: string;
|
|
4808
4304
|
/** Rails this DVM settles over — the menu's funding options. */
|
|
@@ -4818,31 +4314,19 @@ interface BuildCreditMenuArgs {
|
|
|
4818
4314
|
intent: "charge" | "session";
|
|
4819
4315
|
reason: string;
|
|
4820
4316
|
}[];
|
|
4821
|
-
/**
|
|
4822
|
-
* x402 flavours this DVM can actually accept a top-up on (internal-review) — one
|
|
4823
|
-
* server read, so what the sub-block advertises is what `/v1/credit` will
|
|
4824
|
-
* answer a 402 with.
|
|
4825
|
-
*/
|
|
4826
4317
|
x402Schemes?: {
|
|
4827
4318
|
scheme: string;
|
|
4828
4319
|
network: string;
|
|
4829
4320
|
}[];
|
|
4830
4321
|
/**
|
|
4831
|
-
*
|
|
4832
|
-
* the
|
|
4833
|
-
*
|
|
4834
|
-
* menu: a rail whose minimum we can't state honestly is a rail we must not
|
|
4835
|
-
* advertise, but the other rails' sale survives.
|
|
4322
|
+
* Configured raw receive floor in sats, rendered with an FX margin for the menu. Failure
|
|
4323
|
+
* omits Lightning while allowing the rest of the menu; issuance checks the raw floor at
|
|
4324
|
+
* current request-time rates.
|
|
4836
4325
|
*/
|
|
4837
4326
|
lightningFundingMinSats?: number;
|
|
4838
4327
|
/**
|
|
4839
|
-
*
|
|
4840
|
-
*
|
|
4841
|
-
* the filter in {@link buildCreditMenu}.
|
|
4842
|
-
*
|
|
4843
|
-
* Read off the **configured** mint list, never the health-filtered
|
|
4844
|
-
* advertised one: a mint blip must not make a funding rail appear and
|
|
4845
|
-
* disappear between quotes.
|
|
4328
|
+
* Read the configured accepted mint list, not its health-filtered advertisement. Non-channel
|
|
4329
|
+
* Bitcoin refunds need a mint, but configuration alone does not establish refund solvency.
|
|
4846
4330
|
*/
|
|
4847
4331
|
hasAcceptedMint?: boolean;
|
|
4848
4332
|
fxFetcher: FxFetcher;
|
|
@@ -4855,50 +4339,18 @@ interface BuildCreditMenuArgs {
|
|
|
4855
4339
|
callerPubkey?: string;
|
|
4856
4340
|
}
|
|
4857
4341
|
/**
|
|
4858
|
-
*
|
|
4859
|
-
*
|
|
4860
|
-
* because advertising anyway would misadvertise:
|
|
4861
|
-
*
|
|
4862
|
-
* 1. **Isolate runtime** (credits spec §3) — checked first and unconditionally,
|
|
4863
|
-
* before config is even read. Isolate DVMs have no Postgres of their own;
|
|
4864
|
-
* their only DVM-side persistence is the last-write-wins callback KV, which
|
|
4865
|
-
* spec §2 condition 2 prohibits for credit state. Until [internal-review] resolves
|
|
4866
|
-
* the gate they run implicit N=1 only, and their quote simply never offers
|
|
4867
|
-
* credit so nothing is misadvertised.
|
|
4868
|
-
* 2. **Not opted in** — no `credit` block on `configureDVM`.
|
|
4869
|
-
* 3. **No descriptor auth** — a credit belongs to a verified secp256k1 pubkey.
|
|
4870
|
-
* Without auth the ledger's funder identity degrades to the requester id or
|
|
4871
|
-
* `"anonymous"` (see `resolveLedgerContext`), which would pool every
|
|
4872
|
-
* anonymous caller's money into one balance. Advertising a per-caller
|
|
4873
|
-
* balance on such a DVM would be a lie at best and a disclosure at worst.
|
|
4874
|
-
* 4. **Non-durable ledger outside devMode** — a `MemoryCreditLedger` is
|
|
4875
|
-
* per-process, so a caller funding on machine A and drawing on machine B
|
|
4876
|
-
* gets `credit_not_found`. Advertise only what survives the fleet.
|
|
4877
|
-
* 5. **Bounds not expressible in `currency`** — an fx outage, or a currency the
|
|
4878
|
-
* snapshot doesn't carry. Omit rather than quote a number we can't state.
|
|
4342
|
+
* Return no menu for static ineligibility or unresolvable denomination bounds. Isolates
|
|
4343
|
+
* currently lack durable credit storage; anonymous balances must not be advertised.
|
|
4879
4344
|
*
|
|
4880
|
-
*
|
|
4881
|
-
* same-currency credit.
|
|
4882
|
-
*
|
|
4883
|
-
* Rungs 1–4 live in {@link creditMenuConfigured}, which the capability refresh
|
|
4884
|
-
* reads too.
|
|
4345
|
+
* Select the caller echo only from live, same-currency credits with spendable value.
|
|
4885
4346
|
*/
|
|
4886
4347
|
declare function buildCreditMenu(args: BuildCreditMenuArgs): Promise<CreditMenu | undefined>;
|
|
4887
4348
|
/**
|
|
4888
|
-
*
|
|
4889
|
-
*
|
|
4890
|
-
* this list grows with each paid job.
|
|
4891
|
-
*
|
|
4892
|
-
* Live, same-currency credits with something left in them; largest spendable
|
|
4893
|
-
* balance wins, newest breaks a tie. That is the credit a caller would in fact
|
|
4894
|
-
* draw against next. A failed job's released hold leaves a positive implicit
|
|
4895
|
-
* balance, which is real caller money (spec §1, no debit on failure) and is
|
|
4896
|
-
* echoed like any other.
|
|
4349
|
+
* Choose the largest live, active, same-currency spendable balance; newest creation breaks
|
|
4350
|
+
* a tie. Released implicit payments can hold reclaimable value and are eligible too.
|
|
4897
4351
|
*
|
|
4898
|
-
*
|
|
4899
|
-
*
|
|
4900
|
-
* under one `caller_pubkey`, so echoing it would show one caller another's
|
|
4901
|
-
* balance.
|
|
4352
|
+
* The caller supplies a verified pubkey. Anonymous/missing identity returns no credit; the
|
|
4353
|
+
* ledger’s anonymous pool must not be exposed to an individual caller.
|
|
4902
4354
|
*/
|
|
4903
4355
|
declare function selectPrimaryCredit(args: {
|
|
4904
4356
|
ledger: CreditLedgerLike;
|
|
@@ -4906,20 +4358,15 @@ declare function selectPrimaryCredit(args: {
|
|
|
4906
4358
|
currency: string;
|
|
4907
4359
|
nowMs?: number;
|
|
4908
4360
|
}): Promise<CreditSnapshot | undefined>;
|
|
4909
|
-
/** Narrow a ledger snapshot to the read-only shape a quote handler receives. */
|
|
4910
4361
|
declare function toCreditView(snapshot: CreditSnapshot): CreditView;
|
|
4911
4362
|
/**
|
|
4912
|
-
*
|
|
4913
|
-
*
|
|
4914
|
-
* checks presence, and the isolate tier depends on the key never appearing.
|
|
4363
|
+
* Omit the credit key entirely when there is no menu; consumers use presence to determine
|
|
4364
|
+
* whether credit is offered.
|
|
4915
4365
|
*/
|
|
4916
4366
|
declare function attachCreditMenu(body: Record<string, unknown>, menu: CreditMenu | undefined): Record<string, unknown>;
|
|
4917
4367
|
/**
|
|
4918
|
-
*
|
|
4919
|
-
*
|
|
4920
|
-
* This is an explicit whitelist rather than object rest syntax: a later
|
|
4921
|
-
* caller-specific field added to {@link CreditMenu} cannot cross this boundary
|
|
4922
|
-
* until someone deliberately adds it to {@link CreditTerms} and this copy.
|
|
4368
|
+
* Explicitly whitelist public /v1/info terms. A future caller-specific menu field cannot
|
|
4369
|
+
* cross this cacheable boundary merely by being added to CreditMenu.
|
|
4923
4370
|
*/
|
|
4924
4371
|
declare function toCreditTerms(menu: CreditMenu): CreditTerms;
|
|
4925
4372
|
|