@dvmkit/sdk 0.1.2-rc.7 → 0.1.4-rc.7
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 +121 -8
- package/dist/chunk-BIFLRKMO.js +87 -0
- package/dist/chunk-BQ2NMWKE.js +160 -0
- package/dist/{chunk-LDTWX7JW.js → chunk-BTZY7VPH.js} +13 -1
- package/dist/{chunk-FJDCFHW5.js → chunk-C6JHBLMW.js} +3 -81
- package/dist/{chunk-EXHBXA4U.js → chunk-DBCLBYHP.js} +13 -1
- package/dist/chunk-E4EVGPDX.js +391 -0
- package/dist/chunk-EDDYHZ6W.js +1010 -0
- package/dist/{chunk-2ABMGUDS.js → chunk-EVBK675R.js} +88 -10
- package/dist/{chunk-P4RUVDU7.js → chunk-H2MEFVH6.js} +12 -141
- package/dist/chunk-KXZUCCEY.js +142 -0
- package/dist/{chunk-LWUR4CGG.js → chunk-MLRCSJYX.js} +11 -3
- package/dist/{chunk-N4VTG3KH.js → chunk-QK3VJNCK.js} +930 -3011
- package/dist/chunk-RW5LP57K.js +44 -0
- package/dist/{chunk-6JZIX5WW.js → chunk-SSSZUVWM.js} +178 -8
- package/dist/{chunk-TKA6ZP4M.js → chunk-U6M3ATSG.js} +56 -426
- package/dist/chunk-VRQDX5P4.js +1742 -0
- package/dist/{chunk-JGGI65I3.js → chunk-Z4BNLUZF.js} +1 -150
- package/dist/{credit-ledger-ED6JXKVD.js → credit-ledger-2DFQHNLB.js} +2 -2
- package/dist/{credit-menu-BM4qCD5U.d.ts → credit-menu-C1ezIFlJ.d.ts} +1699 -1941
- package/dist/{fx-C-liI3oY.d.ts → fx-C6dl2LVI.d.ts} +1 -1
- package/dist/index.d.ts +7 -6
- package/dist/index.js +8 -4
- package/dist/internal/caller.d.ts +10441 -0
- package/dist/internal/caller.js +10078 -0
- package/dist/internal/index.d.ts +2 -10816
- package/dist/internal/index.js +2 -10130
- package/dist/internal/server.d.ts +404 -0
- package/dist/internal/server.js +202 -0
- package/dist/job-store-DHnW4Cg_.d.ts +591 -0
- package/dist/lightning-backend-Ci1nogk_.d.ts +367 -0
- package/dist/{memory-credit-ledger-XJ5VQEVP.js → memory-credit-ledger-OP24Z2KO.js} +3 -3
- package/dist/{postgres-job-store-J5F4GUWU.js → postgres-job-store-3RAXMNSY.js} +1 -1
- package/dist/{revenue-reporter-JIKUPXOK.js → revenue-reporter-ASZ7SHHH.js} +1 -1
- package/dist/server/index.d.ts +41 -11
- package/dist/server/index.js +93 -69
- package/dist/{job-store-C53VQ5uu.d.ts → step-cache-BLPZNizw.d.ts} +176 -585
- package/dist/{tempo-session-store-DALMRIWN.js → tempo-session-store-2JNOKJGX.js} +2 -2
- package/dist/testing/index.d.ts +25 -3
- package/dist/testing/index.js +36 -3
- package/dist/{usd-gLcJB1ps.d.ts → usd-BgOfZlk6.d.ts} +1 -1
- package/dist/wallet-CJC8lwxx.d.ts +29 -0
- package/dist/{x402-FTG2GRAQ.js → x402-T2C5MX3T.js} +6 -3
- package/package.json +11 -5
- package/dist/{chunk-RU7SXHLO.js → chunk-UP2F5RRT.js} +3 -3
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import { ProofLike } from '@cashu/cashu-ts';
|
|
2
|
+
import { a1 as Message, ah as FundingMethod, X as FundingReceipt, bO as CreditSnapshot, Y as JobReceipt, ax as StepRecord, a2 as MessageType } from './step-cache-BLPZNizw.js';
|
|
3
|
+
|
|
4
|
+
/** Job status values that can be persisted. */
|
|
5
|
+
type JobStatus = "processing" | "completed" | "failed" | "awaiting-input" | "cancelled" | "working";
|
|
6
|
+
/** Serializable snapshot of a job's persistent state. */
|
|
7
|
+
interface JobRecord {
|
|
8
|
+
id: string;
|
|
9
|
+
tags: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Capability name this job dispatches to (internal-review). Populated from the
|
|
12
|
+
* `POST /v1/job` body's `capability` field at submission time; persisted
|
|
13
|
+
* so reactivation on another machine routes to the same handler.
|
|
14
|
+
*/
|
|
15
|
+
capability: string;
|
|
16
|
+
input: string;
|
|
17
|
+
params: Record<string, string>;
|
|
18
|
+
requesterId: string;
|
|
19
|
+
/**
|
|
20
|
+
* SHA-256 hex of the per-job opaque token issued at creation time for
|
|
21
|
+
* anonymous requesters (internal-review). When set, all `/v1/job/:id/*` reads/writes
|
|
22
|
+
* gate on the caller presenting the matching `X-Job-Token` header — this is
|
|
23
|
+
* the mechanism that prevents cross-caller transcript leaks for free DVMs
|
|
24
|
+
* where every caller otherwise resolves to `requesterId === "anonymous"`.
|
|
25
|
+
* Undefined for authenticated jobs (bearer / signed-request), which gate on
|
|
26
|
+
* `requesterId` instead.
|
|
27
|
+
*/
|
|
28
|
+
requesterTokenHash?: string;
|
|
29
|
+
/**
|
|
30
|
+
* The `job_token` itself, in the clear (internal-review). Kept so a caller whose
|
|
31
|
+
* paid submit timed out can replay it and be handed back the very token the
|
|
32
|
+
* lost response carried — the alternative (minting a fresh one) would be
|
|
33
|
+
* rejected by whichever machine is still running the job, since its
|
|
34
|
+
* `activeJobs` copy holds the original hash.
|
|
35
|
+
*
|
|
36
|
+
* The cost is that DB read access now also confers job *control* (cancel,
|
|
37
|
+
* feed input), not just read access. Accepted: the same database holds the
|
|
38
|
+
* accumulator's Cashu proofs, which are bearer money — a reader who gets
|
|
39
|
+
* that far has strictly better targets. `requesterTokenHash` remains the
|
|
40
|
+
* only thing the ownership check reads.
|
|
41
|
+
*/
|
|
42
|
+
requesterToken?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Fingerprint of the submission that created this job (internal-review) — see
|
|
45
|
+
* `requestFingerprint`. The replay gate: a retried paid submit is handed the
|
|
46
|
+
* original job only when it reproduces this.
|
|
47
|
+
*/
|
|
48
|
+
requestFingerprint?: string;
|
|
49
|
+
/**
|
|
50
|
+
* secp256k1 x-only pubkey of the caller that signed the submission, on
|
|
51
|
+
* DVMs with descriptor-level auth (internal-review). Second half of the replay
|
|
52
|
+
* gate — the signature envelope is re-minted on every resume attempt, so
|
|
53
|
+
* the identity behind it is pinned here instead.
|
|
54
|
+
*/
|
|
55
|
+
requesterPubkey?: string;
|
|
56
|
+
/** Caller-generated public recovery id bound to this request fingerprint. */
|
|
57
|
+
requestId?: string;
|
|
58
|
+
/** HTTP path whose live auth gate accepted the persisted caller proof. */
|
|
59
|
+
authRequestPath?: string;
|
|
60
|
+
status: JobStatus;
|
|
61
|
+
summary?: string;
|
|
62
|
+
messages: Message[];
|
|
63
|
+
seq: number;
|
|
64
|
+
paidMsats: number;
|
|
65
|
+
paymentMint?: string;
|
|
66
|
+
/** Rail of the most recent successful credit (cashu / x402 / mpp). */
|
|
67
|
+
paymentRail?: FundingMethod;
|
|
68
|
+
/**
|
|
69
|
+
* Settlement reference threaded into the platform revenue ledger
|
|
70
|
+
* (`revenue_events.tx_hash`). EVM tx hash for x402, mppx `challenge.id`
|
|
71
|
+
* for mpp (internal-review); cashu accumulator path sets this to the
|
|
72
|
+
* `X-Cashu-Request-Id` UUID (internal-review). Persisted so it survives durable
|
|
73
|
+
* replay.
|
|
74
|
+
*/
|
|
75
|
+
paymentTxHash?: string;
|
|
76
|
+
/** Chain transaction returned to callers recovering x402 or Tempo acceptance. */
|
|
77
|
+
paymentTransactionHash?: string;
|
|
78
|
+
/** Rail-native upfront amount (sats / USDC microunits). internal-review. */
|
|
79
|
+
nativeAmount?: number;
|
|
80
|
+
/** Native asset tag — paired with `nativeAmount`. internal-review. */
|
|
81
|
+
nativeAsset?: "sats" | "usdc" | "usdc.e" | "usd-cents";
|
|
82
|
+
/** Cashu flow discriminator written into `revenue_events.metadata.cashu_flow` (internal-review). */
|
|
83
|
+
cashuFlow?: "p2pk_accumulator";
|
|
84
|
+
/**
|
|
85
|
+
* What this job cost the **builder** to serve, in 1e-6 of
|
|
86
|
+
* {@link JobRecord.costCurrency} (internal-review). Mirror of
|
|
87
|
+
* `ServerJob.costAmountMicro`; see that field. Absent means unreported,
|
|
88
|
+
* which the platform treats differently from a declared zero.
|
|
89
|
+
*/
|
|
90
|
+
costAmountMicro?: number;
|
|
91
|
+
/** Currency {@link JobRecord.costAmountMicro} is 1e-6 of (lowercase ISO-4217). */
|
|
92
|
+
costCurrency?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Number of `ctx.cost()` declarations incorporated into the current cost
|
|
95
|
+
* state. Lets the platform deterministically replace a stale persisted
|
|
96
|
+
* prefix with the handler owner's later cumulative value.
|
|
97
|
+
*/
|
|
98
|
+
costRevision?: number;
|
|
99
|
+
/**
|
|
100
|
+
* Credit the upfront payment funded/drew (internal-review). The terminal funnel
|
|
101
|
+
* settles the draw on success and releases it on failure/cancel, and the
|
|
102
|
+
* receipt countersigns the `ReceiptCredit` block from it.
|
|
103
|
+
*/
|
|
104
|
+
creditId?: string;
|
|
105
|
+
/** The draw placed for this job on `creditId` (internal-review). */
|
|
106
|
+
drawId?: string;
|
|
107
|
+
/** Funding-time evidence returned on explicit fund-and-draw submissions. */
|
|
108
|
+
fundingReceipt?: FundingReceipt;
|
|
109
|
+
fundingCredit?: CreditSnapshot;
|
|
110
|
+
receivedProofs: ProofLike[];
|
|
111
|
+
pendingPaymentMsats?: number;
|
|
112
|
+
/**
|
|
113
|
+
* Per-job MPP credential binding (internal-review). Challenge ids issued in the
|
|
114
|
+
* most-recent `requestPayment` yield; the SDK rejects mid-job credentials
|
|
115
|
+
* whose `challenge.id` isn't in this set.
|
|
116
|
+
*
|
|
117
|
+
* Keeps its `mpp` spelling past internal-review's rail rename (internal-review). The ids
|
|
118
|
+
* are mppx challenge ids — objects of the MPP envelope the Tempo rail rides,
|
|
119
|
+
* which is not itself a rail — and this is server-internal anti-replay state
|
|
120
|
+
* that is never serialised to a caller. It persists as
|
|
121
|
+
* `jobs.pending_mpp_challenge_ids` / `isolate_jobs.pending_mpp_challenge_ids`.
|
|
122
|
+
*/
|
|
123
|
+
pendingMppChallengeIds?: string[];
|
|
124
|
+
/**
|
|
125
|
+
* Per-job x402 credential binding (internal-review). 0x-prefixed 32-byte hex nonce
|
|
126
|
+
* issued in the most-recent `requestPayment` yield; the SDK rejects mid-job
|
|
127
|
+
* x402 payments whose decoded `authorization.nonce` differs from this value.
|
|
128
|
+
*/
|
|
129
|
+
pendingX402Nonce?: string;
|
|
130
|
+
/**
|
|
131
|
+
* Exact USDC microunit amount advertised with `pendingX402Nonce`. Verification
|
|
132
|
+
* reuses this value so an FX refresh between request and payment cannot change
|
|
133
|
+
* the facilitator requirements.
|
|
134
|
+
*/
|
|
135
|
+
pendingX402AmountUsdcMicro?: string;
|
|
136
|
+
/**
|
|
137
|
+
* The outstanding `requestPayment` ask denominated in fiat micro-units, pinned
|
|
138
|
+
* when the payment-request was emitted (internal-review) — the mid-job analogue of
|
|
139
|
+
* `resolvePriceFiat` pinning the quote. The ledger is fiat-denominated, so the
|
|
140
|
+
* top-up's `fund` + `growDraw` need a figure that doesn't move with the BTC/USD
|
|
141
|
+
* rate between the ask and the payment. Absent when the ask couldn't be priced
|
|
142
|
+
* in fiat (cold-start fx outage), which degrades that top-up to ledger-less.
|
|
143
|
+
*/
|
|
144
|
+
pendingPaymentFiatMicro?: number;
|
|
145
|
+
/** Currency of {@link pendingPaymentFiatMicro} (lowercase ISO-4217). */
|
|
146
|
+
pendingPaymentFiatCurrency?: string;
|
|
147
|
+
/**
|
|
148
|
+
* What this job has **cumulatively** asked for mid-job, in fiat micro-units
|
|
149
|
+
* of {@link askedTopUpCurrency} (internal-review) — the ceiling `growDraw` caps the
|
|
150
|
+
* job's draw growth at. Monotonic: written by the same Tx A that emits each
|
|
151
|
+
* payment-request, and never decremented, which is exactly what
|
|
152
|
+
* `pendingPaymentFiatMicro` is not (Tx C drains it as payments land). A cap
|
|
153
|
+
* derived from the outstanding figure would let a duplicate payment arriving
|
|
154
|
+
* in the window before Tx C see a full ask that the winner had already
|
|
155
|
+
* bought.
|
|
156
|
+
*
|
|
157
|
+
* `-1` is a sticky "not expressible" sentinel — after internal-review the one door
|
|
158
|
+
* left to it is an ask the rate provider was down for. Undefined means no
|
|
159
|
+
* mid-job ask has been recorded (including a row written before this
|
|
160
|
+
* existed). Either way the cap is skipped and growth is unbounded, as it was
|
|
161
|
+
* before: a guessed ceiling on a money path is worse than none.
|
|
162
|
+
*/
|
|
163
|
+
askedTopUpMicro?: number;
|
|
164
|
+
/**
|
|
165
|
+
* Currency of {@link askedTopUpMicro} (lowercase ISO-4217) — and, from
|
|
166
|
+
* internal-review, the job's own ask denomination: first-write-wins, and read back
|
|
167
|
+
* by `ctx.requestPayment` so every later ask pins in it too. That is what
|
|
168
|
+
* makes a mid-job currency switch unexpressible rather than merely unlikely.
|
|
169
|
+
*/
|
|
170
|
+
askedTopUpCurrency?: string;
|
|
171
|
+
/**
|
|
172
|
+
* Why this job's draw growth is **not** capped, when it isn't (internal-review) —
|
|
173
|
+
* the durable half of the `-1` sentinel, so an operator can list the affected
|
|
174
|
+
* jobs after the fact rather than reconstruct them from log lines. Sticky
|
|
175
|
+
* first-writer-wins, like the sentinel itself. Null on every job whose ceiling
|
|
176
|
+
* is enforceable, which is the overwhelming majority.
|
|
177
|
+
*/
|
|
178
|
+
topUpCapUnenforcedReason?: TopUpCapUnenforcedReason;
|
|
179
|
+
/** Price the job was charged at (msats). Used to detect overpayment for change/melt gating. */
|
|
180
|
+
requiredMsats?: number;
|
|
181
|
+
/**
|
|
182
|
+
* The DVM-signed proof of this job's outcome (internal-review). Written once at the
|
|
183
|
+
* terminal transition via `saveReceipt` and never rewritten — `save` leaves
|
|
184
|
+
* the column alone so a stale in-memory snapshot can't clear it. Absent on
|
|
185
|
+
* non-terminal jobs, and on every job when the DVM has no receipt key wired.
|
|
186
|
+
*/
|
|
187
|
+
receipt?: JobReceipt;
|
|
188
|
+
stepCache: StepRecord[];
|
|
189
|
+
state: unknown;
|
|
190
|
+
createdAt: number;
|
|
191
|
+
lastActivityAt: number;
|
|
192
|
+
/** When caller-controlled content was removed by the retention sweep. */
|
|
193
|
+
redactedAt?: number;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Counter snapshot used by the NOTIFY-driven resolver and the
|
|
197
|
+
* `verifyAndCredit` flow (internal-review). Carries just the handler-visible state
|
|
198
|
+
* needed to decide whether a pending payment yield can resolve, plus (internal-review)
|
|
199
|
+
* the terminal reason so a machine adopting another machine's cancel can hand
|
|
200
|
+
* the caller's own words to `ctx.signal.reason` and the `onCancel` hook.
|
|
201
|
+
*/
|
|
202
|
+
interface JobCounters {
|
|
203
|
+
status: JobStatus;
|
|
204
|
+
paidMsats: number;
|
|
205
|
+
pendingPaymentMsats?: number;
|
|
206
|
+
/**
|
|
207
|
+
* `jobs.summary`. Set to the cancel/fail reason by `cancelJob` /
|
|
208
|
+
* `cancelStaleJob`, and to the completion summary by a handler's
|
|
209
|
+
* `ctx.complete(summary)`. Undefined when nothing recorded one.
|
|
210
|
+
*/
|
|
211
|
+
summary?: string;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Credit delta written inside the Tx C transaction (internal-review) when an
|
|
215
|
+
* inbound payment verifies. Fields mirror what the rails produce in
|
|
216
|
+
* `payment.ts`; `paidMsatsDelta` is the only required key.
|
|
217
|
+
*/
|
|
218
|
+
interface PaymentCreditDelta {
|
|
219
|
+
paidMsatsDelta: number;
|
|
220
|
+
/** When true, set `pending_payment_msats = NULL`; otherwise decrement by the delta. */
|
|
221
|
+
clearPending?: boolean;
|
|
222
|
+
paymentMint?: string;
|
|
223
|
+
paymentRail?: FundingMethod;
|
|
224
|
+
paymentTxHash?: string;
|
|
225
|
+
nativeAmount?: number;
|
|
226
|
+
nativeAsset?: "sats" | "usdc" | "usdc.e" | "usd-cents";
|
|
227
|
+
cashuFlow?: "p2pk_accumulator";
|
|
228
|
+
/** When true, accumulate `nativeAmount` onto the existing same-rail value; otherwise overwrite. */
|
|
229
|
+
accumulateNative?: boolean;
|
|
230
|
+
/** Clear the pending nonce + advertised amount after an x402 credit succeeds. */
|
|
231
|
+
clearPendingX402Binding?: boolean;
|
|
232
|
+
/**
|
|
233
|
+
* Credit this payment funded (internal-review). Only ever set when the mid-job
|
|
234
|
+
* top-up minted a *fresh* implicit credit — a free-then-paid job whose
|
|
235
|
+
* upfront leg placed no draw. Written set-if-null: a job already bound to a
|
|
236
|
+
* credit keeps that binding, because the top-up grew its existing draw
|
|
237
|
+
* rather than opening a second one.
|
|
238
|
+
*/
|
|
239
|
+
creditId?: string;
|
|
240
|
+
/** The draw opened alongside {@link creditId}. Same set-if-null rule. */
|
|
241
|
+
drawId?: string;
|
|
242
|
+
}
|
|
243
|
+
/** Atomic side effects applied with an outgoing provider message. */
|
|
244
|
+
interface AppendOutgoingOptions {
|
|
245
|
+
pendingPaymentDelta?: number;
|
|
246
|
+
pendingMppChallengeIds?: string[];
|
|
247
|
+
pendingX402Nonce?: string;
|
|
248
|
+
pendingX402AmountUsdcMicro?: string;
|
|
249
|
+
/**
|
|
250
|
+
* Fiat envelope pinned for this ask (internal-review). Accumulates alongside
|
|
251
|
+
* `pendingPaymentDelta` when the currency matches what's already recorded,
|
|
252
|
+
* and replaces it when it doesn't — the same shape `pending_payment_msats`
|
|
253
|
+
* uses, so stacked asks stay summable.
|
|
254
|
+
*/
|
|
255
|
+
pendingPaymentFiatMicro?: number;
|
|
256
|
+
pendingPaymentFiatCurrency?: string;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Why a job's cumulative ask total can't bound its draw growth (internal-review).
|
|
260
|
+
*
|
|
261
|
+
* - `ask_unpriceable` — an ask the rate provider was down for, or one whose
|
|
262
|
+
* fiat figure isn't a safe integer. The only door left after internal-review, and
|
|
263
|
+
* deliberately still open: failing the charge to protect a ledger comparison
|
|
264
|
+
* would break a payment that doesn't need the rate to be quoted or collected.
|
|
265
|
+
* - `ask_currency_switch` — two asks on one job in different denominations.
|
|
266
|
+
* Unreachable through `ctx.requestPayment`, which pins every ask in the job's
|
|
267
|
+
* own sticky denomination; kept as the backstop for a record written by an
|
|
268
|
+
* older build, and because the accumulator must not silently sum two units.
|
|
269
|
+
*/
|
|
270
|
+
type TopUpCapUnenforcedReason = "ask_unpriceable" | "ask_currency_switch";
|
|
271
|
+
/**
|
|
272
|
+
* Result of a Tx C `verifyAndCredit` call. `alreadyVerified` is set when the
|
|
273
|
+
* inbound row was previously marked verified (idempotency guard); the credit
|
|
274
|
+
* is skipped in that case.
|
|
275
|
+
*/
|
|
276
|
+
interface VerifyAndCreditResult {
|
|
277
|
+
counters: JobCounters;
|
|
278
|
+
alreadyVerified: boolean;
|
|
279
|
+
/**
|
|
280
|
+
* The credit the job row holds **after** this call — the authoritative
|
|
281
|
+
* answer, read back rather than assumed (internal-review).
|
|
282
|
+
*
|
|
283
|
+
* {@link PaymentCreditDelta.creditId} is written set-if-null, so a caller
|
|
284
|
+
* that passes one cannot know whether it won the bind: on a free-then-paid
|
|
285
|
+
* job two concurrent top-ups each mint their own implicit credit and only
|
|
286
|
+
* one lands. Reading it back is what lets `processPayment` mirror the row
|
|
287
|
+
* truthfully and name the loser's deposit to the caller instead of silently
|
|
288
|
+
* recording a binding the row rejected.
|
|
289
|
+
*/
|
|
290
|
+
creditId?: string;
|
|
291
|
+
/** The draw the job row holds after this call. Same read-back rule. */
|
|
292
|
+
drawId?: string;
|
|
293
|
+
}
|
|
294
|
+
/** Secret-free identity for atomically reserving one public request id. */
|
|
295
|
+
interface RequestIdClaim {
|
|
296
|
+
/** Caller-generated id carried inside the signed request body. */
|
|
297
|
+
requestId: string;
|
|
298
|
+
/** Verified x-only secp256k1 caller public key. */
|
|
299
|
+
requesterPubkey: string;
|
|
300
|
+
/** Server-resolved requester identity used by normal job ownership checks. */
|
|
301
|
+
requesterId: string;
|
|
302
|
+
/** Fingerprint of the request body with only the expiring auth fields removed. */
|
|
303
|
+
requestFingerprint: string;
|
|
304
|
+
/** Job id allocated before payment and reused by every exact recovery attempt. */
|
|
305
|
+
jobId: string;
|
|
306
|
+
/** Server-generated owner token for the live serialization lock. */
|
|
307
|
+
claimToken: string;
|
|
308
|
+
}
|
|
309
|
+
/** Result of acquiring the live lock for a durable public request-id binding. */
|
|
310
|
+
interface RequestIdClaimResult {
|
|
311
|
+
/** Stable job id first assigned to this caller/request id. */
|
|
312
|
+
jobId: string;
|
|
313
|
+
/** True when the durable identity binding existed before this lock acquisition. */
|
|
314
|
+
replayed: boolean;
|
|
315
|
+
}
|
|
316
|
+
/** Persistent backing store for job records. */
|
|
317
|
+
interface JobStore {
|
|
318
|
+
/** Retrieve a job record by ID. Returns undefined if not found. */
|
|
319
|
+
get(id: string): Promise<JobRecord | undefined>;
|
|
320
|
+
/**
|
|
321
|
+
* Retrieve the job a settlement reference paid for (internal-review). On the cashu
|
|
322
|
+
* accumulator path `paymentTxHash` is the caller's `X-Cashu-Request-Id`, so
|
|
323
|
+
* this is what makes a retried paid submit idempotent: the replay finds the
|
|
324
|
+
* job its (already-spent) payment created instead of a bare 409.
|
|
325
|
+
*/
|
|
326
|
+
findJobByPaymentTxHash(txHash: string): Promise<JobRecord | undefined>;
|
|
327
|
+
/** Read a signed caller's job by public recovery id, when the store supports it. */
|
|
328
|
+
findJobByRequestId?(requestId: string, requesterPubkey: string): Promise<JobRecord | undefined>;
|
|
329
|
+
/**
|
|
330
|
+
* Atomically bind and lock a caller/request id before pricing, payment
|
|
331
|
+
* verification, credit draw, or handler work. Returns undefined when
|
|
332
|
+
* another live request holds it or its durable binding has different
|
|
333
|
+
* provenance. A process death releases the live lock but not the binding.
|
|
334
|
+
*/
|
|
335
|
+
claimRequestId?(claim: RequestIdClaim): Promise<RequestIdClaimResult | undefined>;
|
|
336
|
+
/**
|
|
337
|
+
* Lock an existing exact caller/request binding without creating one.
|
|
338
|
+
* Used only by the explicit recovery route, which must prove the original
|
|
339
|
+
* submit reached the server before it can inspect durable acceptance state.
|
|
340
|
+
*/
|
|
341
|
+
resumeRequestId?(claim: RequestIdClaim): Promise<RequestIdClaimResult | undefined>;
|
|
342
|
+
/**
|
|
343
|
+
* Release the live serialization lock after this route finishes. The opaque
|
|
344
|
+
* owner token prevents a delayed request from releasing a newer holder; the
|
|
345
|
+
* durable caller/fingerprint/job binding remains retained.
|
|
346
|
+
*/
|
|
347
|
+
releaseRequestIdClaim?(claim: RequestIdClaim): Promise<void>;
|
|
348
|
+
/** Upsert a job record. */
|
|
349
|
+
save(record: JobRecord): Promise<void>;
|
|
350
|
+
/** Delete a job record. */
|
|
351
|
+
delete(id: string): Promise<void>;
|
|
352
|
+
}
|
|
353
|
+
/** Total-order cursor for terminal job retention scans. */
|
|
354
|
+
interface JobRetentionCursor {
|
|
355
|
+
lastActivityAt: number;
|
|
356
|
+
id: string;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* JobStore extension for signed job receipts (internal-review). Implemented by both
|
|
360
|
+
* container-tier stores (`MemoryJobStore`, `PostgresJobStore`); the isolate
|
|
361
|
+
* tier's `IsolateJobStore` deliberately doesn't, which is what keeps the
|
|
362
|
+
* out-of-scope isolate gap a compile-time fact rather than a runtime surprise.
|
|
363
|
+
*/
|
|
364
|
+
interface ReceiptIssuingStore {
|
|
365
|
+
/**
|
|
366
|
+
* Allocate this job's receipt sequence number, or return the one it already
|
|
367
|
+
* holds. Idempotent per job and monotonic per DVM: two machines racing to
|
|
368
|
+
* issue the same job's receipt get the same number, and the loser burns no
|
|
369
|
+
* sequence.
|
|
370
|
+
*
|
|
371
|
+
* Gaps in the issued sequence are the completeness signal that exposes
|
|
372
|
+
* receipt suppression, so a read-then-write counter (which can hand two jobs
|
|
373
|
+
* the same number under concurrency) is not acceptable — the allocation has
|
|
374
|
+
* to be atomic against every other machine.
|
|
375
|
+
*/
|
|
376
|
+
claimReceiptSeq(jobId: string): Promise<number>;
|
|
377
|
+
/**
|
|
378
|
+
* Persist a signed receipt iff the job doesn't already have one, returning
|
|
379
|
+
* whichever receipt is stored afterwards. Write-once: a racing second issuer
|
|
380
|
+
* gets the winner's bytes back, so every reader of the job — sync response,
|
|
381
|
+
* poll, later re-read — sees byte-identical JSON.
|
|
382
|
+
*
|
|
383
|
+
* Returns `undefined` when the job row is gone.
|
|
384
|
+
*/
|
|
385
|
+
saveReceipt(jobId: string, receipt: JobReceipt): Promise<JobReceipt | undefined>;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* JobStore extension for cross-machine message streaming + the internal-review
|
|
389
|
+
* transactional model (Tx A / Tx B / Tx C).
|
|
390
|
+
*
|
|
391
|
+
* Tx A — outgoing message + immediate counter consequence (`appendOutgoing`).
|
|
392
|
+
* Single tx: server-allocate seq, append `job_messages` row with
|
|
393
|
+
* `status='verified'`, bump `pending_payment_msats` for payment-request
|
|
394
|
+
* messages, and `pg_notify`.
|
|
395
|
+
*
|
|
396
|
+
* Tx B — incoming message marked `pending-verification` (`recordInbound`).
|
|
397
|
+
* Single tx: server-allocate seq, append `job_messages` row with
|
|
398
|
+
* `status='pending-verification'`, and `pg_notify`. External verification
|
|
399
|
+
* (Cashu mint, x402 facilitator, MPP server) runs OUTSIDE the tx.
|
|
400
|
+
*
|
|
401
|
+
* Tx C — verification outcome + credit (`verifyAndCredit`). Single tx:
|
|
402
|
+
* flip the `job_messages.status` to `'verified'`, bump `paid_msats` /
|
|
403
|
+
* clear `pending_payment_msats` per the credit delta, and `pg_notify`.
|
|
404
|
+
* Idempotent — a repeat call returns `alreadyVerified: true` without
|
|
405
|
+
* re-crediting.
|
|
406
|
+
*/
|
|
407
|
+
interface StreamableJobStore extends JobStore {
|
|
408
|
+
/**
|
|
409
|
+
* Tx A — append an outgoing message and apply its immediate counter
|
|
410
|
+
* consequence. Returns the server-allocated seq. The caller's `message`
|
|
411
|
+
* carries no `seq` (it's allocated inside the tx). For payment-request
|
|
412
|
+
* messages, pass the amount via `pendingPaymentDelta` so the bump and
|
|
413
|
+
* the message land in the same tx.
|
|
414
|
+
*/
|
|
415
|
+
appendOutgoing(jobId: string, message: OutgoingMessage, opts?: AppendOutgoingOptions): Promise<number>;
|
|
416
|
+
/**
|
|
417
|
+
* Tx B — record an inbound message durably with `pending-verification`
|
|
418
|
+
* status. Returns the server-allocated seq.
|
|
419
|
+
*/
|
|
420
|
+
recordInbound(jobId: string, message: OutgoingMessage): Promise<number>;
|
|
421
|
+
/**
|
|
422
|
+
* Tx C — flip an inbound message from `pending-verification` to either
|
|
423
|
+
* `verified` (with a credit delta when it's a payment) or `failed-verify`
|
|
424
|
+
* (when verification rejected). Idempotent: a repeat call with a row
|
|
425
|
+
* already in `verified`/`failed-verify` returns `alreadyVerified: true`
|
|
426
|
+
* and skips the credit.
|
|
427
|
+
*
|
|
428
|
+
* Pass `credit: null` for non-payment messages (response/cancel/approval).
|
|
429
|
+
*/
|
|
430
|
+
verifyAndCredit(jobId: string, seq: number, credit: PaymentCreditDelta | null): Promise<VerifyAndCreditResult>;
|
|
431
|
+
/**
|
|
432
|
+
* Read back what Tx C committed for an inbound message, without opening a
|
|
433
|
+
* transaction of its own (internal-review).
|
|
434
|
+
*
|
|
435
|
+
* A `verifyAndCredit` that throws is ambiguous: the error may have been
|
|
436
|
+
* raised at or after its COMMIT, in which case the write is durable and the
|
|
437
|
+
* caller has simply lost the acknowledgement. Retrying resolves that — until
|
|
438
|
+
* the retries run out, and the last attempt's error is as ambiguous as any
|
|
439
|
+
* other's. This is the read that settles it: `undefined` means Tx C has not
|
|
440
|
+
* committed for this seq, anything else is the outcome it committed.
|
|
441
|
+
*
|
|
442
|
+
* Implementations MUST answer without a transaction (no `BEGIN`/`COMMIT`) and
|
|
443
|
+
* MUST NOT write, because the caller reaches for it precisely when writes are
|
|
444
|
+
* failing. `alreadyVerified` is always `true` on the returned value — the
|
|
445
|
+
* credit landed in an earlier call, not this one.
|
|
446
|
+
*/
|
|
447
|
+
getVerifiedInbound(jobId: string, seq: number): Promise<VerifyAndCreditResult | undefined>;
|
|
448
|
+
/**
|
|
449
|
+
* Mark an inbound message as failed-verify without crediting. Used when
|
|
450
|
+
* external verification rejects the payment (Cashu mint says SPENT, x402
|
|
451
|
+
* facilitator returns invalid, mppx HMAC mismatch).
|
|
452
|
+
*/
|
|
453
|
+
markInboundFailed(jobId: string, seq: number, reason: string): Promise<void>;
|
|
454
|
+
/** Read the counters used by the resolver (status / paid / pending). */
|
|
455
|
+
getCounters(jobId: string): Promise<JobCounters | undefined>;
|
|
456
|
+
/**
|
|
457
|
+
* Single-execution claim (internal-review). Atomically flip a job from
|
|
458
|
+
* `awaiting-input` to `processing`, returning `true` only for the caller
|
|
459
|
+
* that won the transition. After a mid-job payment lands cross-machine, the
|
|
460
|
+
* live original handler (woken via NOTIFY) and a reactivation replay on the
|
|
461
|
+
* paying machine both race to drive the job; the CAS winner proceeds and the
|
|
462
|
+
* loser stands down, so a healthy worker never double-executes (double
|
|
463
|
+
* substrate spend, clobbered artifact). Also bumps `last_activity_at` so the
|
|
464
|
+
* claimant is covered by the processing watchdog. A no-op returning `false`
|
|
465
|
+
* for a job that is no longer `awaiting-input` (already claimed / terminal).
|
|
466
|
+
*/
|
|
467
|
+
claimForProcessing(jobId: string): Promise<boolean>;
|
|
468
|
+
/**
|
|
469
|
+
* Run `fn` while holding a tx-scoped advisory lock keyed on the job id.
|
|
470
|
+
* Resolves with `{ acquired: false }` when the lock is already held by
|
|
471
|
+
* another transaction — the caller decides what to do (typically: skip
|
|
472
|
+
* reactivation, the other holder will handle it). The lock releases on
|
|
473
|
+
* commit/rollback of the wrapping tx.
|
|
474
|
+
*/
|
|
475
|
+
tryReactivationLock<T>(jobId: string, fn: () => Promise<T>): Promise<{
|
|
476
|
+
acquired: true;
|
|
477
|
+
result: T;
|
|
478
|
+
} | {
|
|
479
|
+
acquired: false;
|
|
480
|
+
}>;
|
|
481
|
+
/**
|
|
482
|
+
* Subscribe to messages for a job. Delivers existing messages (seq > afterSeq)
|
|
483
|
+
* from the `job_messages` table first, then forwards new ones via NOTIFY.
|
|
484
|
+
* Returns an unsubscribe function.
|
|
485
|
+
*
|
|
486
|
+
* Only messages with `status='verified'` are delivered — pending and
|
|
487
|
+
* failed-verify rows are internal to the verification flow.
|
|
488
|
+
*/
|
|
489
|
+
subscribeMessages(jobId: string, afterSeq: number, onMessage: (msg: Message) => void): Promise<() => void>;
|
|
490
|
+
/**
|
|
491
|
+
* Subscribe to raw NOTIFY events for a job. Fires once per `pg_notify`
|
|
492
|
+
* delivery; no message decoding. Used by `Context.requestPayment` to
|
|
493
|
+
* re-read counters when cross-machine credit lands (internal-review).
|
|
494
|
+
*/
|
|
495
|
+
subscribeNotifications(jobId: string, onNotify: () => void): Promise<() => void>;
|
|
496
|
+
/** Read messages from the `job_messages` table for a job. */
|
|
497
|
+
getMessages(jobId: string, afterSeq: number): Promise<Message[]>;
|
|
498
|
+
/**
|
|
499
|
+
* Find stale non-terminal jobs (internal-review / internal-review). Two status-aware
|
|
500
|
+
* cutoffs (both Unix ms): `processing`/`working` rows older than
|
|
501
|
+
* `processingThresholdMs` (worker-liveness watchdog — the SDK heartbeats
|
|
502
|
+
* live workers, so a frozen `last_activity_at` here means the worker died),
|
|
503
|
+
* and `awaiting-input` rows older than `awaitingThresholdMs` (the
|
|
504
|
+
* caller-input idle timeout). Pass a negative cutoff to disable that arm.
|
|
505
|
+
* Returns at most `limit` records, oldest first. Consumed exclusively by the
|
|
506
|
+
* `JobManager` stale-job sweeper; not safe to feed into handler dispatch.
|
|
507
|
+
*
|
|
508
|
+
* `capabilities` scopes the query to jobs the calling JobManager owns —
|
|
509
|
+
* multi-mount hosts where two descriptors share a DB must not steal
|
|
510
|
+
* each other's stuck rows (the activeJobs teardown only fires on the
|
|
511
|
+
* descriptor that actually hosted the zombie). Pass `null` only from
|
|
512
|
+
* single-descriptor deployments.
|
|
513
|
+
*/
|
|
514
|
+
findStaleJobs(processingThresholdMs: number, awaitingThresholdMs: number, limit: number, capabilities: string[] | null): Promise<JobRecord[]>;
|
|
515
|
+
/**
|
|
516
|
+
* Atomically transition a stale job to `terminalStatus` (`failed` for a
|
|
517
|
+
* dead worker, `cancelled` for an idle caller-wait) and append a final
|
|
518
|
+
* `cancel` message in the same transaction (internal-review / internal-review). The
|
|
519
|
+
* `expectedActivityBefore` clause is an optimistic CAS — the flip only
|
|
520
|
+
* lands when the job is still non-terminal AND `last_activity_at <=
|
|
521
|
+
* expectedActivityBefore`. Returns true when this caller claimed the
|
|
522
|
+
* transition, false when another machine got there first OR the job's
|
|
523
|
+
* activity bumped (recovered). Mirrors `save`'s terminal cleanup by
|
|
524
|
+
* deleting incremental `job_messages` rows once the snapshot is final.
|
|
525
|
+
*/
|
|
526
|
+
cancelStaleJob(jobId: string, expectedActivityBefore: number, reason: string, terminalStatus: "failed" | "cancelled"): Promise<boolean>;
|
|
527
|
+
/**
|
|
528
|
+
* Atomically cancel a job from a machine that isn't running its handler
|
|
529
|
+
* (internal-review). Same transaction shape as {@link cancelStaleJob} minus the
|
|
530
|
+
* activity cutoff — the CAS guards only on the job still being non-terminal,
|
|
531
|
+
* so this replaces the read-then-`save` the DELETE route used to do (a TOCTOU
|
|
532
|
+
* against a concurrent terminal write).
|
|
533
|
+
*
|
|
534
|
+
* The `cancel` message and the terminal status commit together and `pg_notify`
|
|
535
|
+
* fires inside the tx, so the machine actually running the handler wakes on
|
|
536
|
+
* its per-job notification subscription, adopts the terminal status, and
|
|
537
|
+
* aborts `ctx.signal` within a NOTIFY round-trip instead of at the handler's
|
|
538
|
+
* next store write. Returns false when the job was already terminal (another
|
|
539
|
+
* machine won).
|
|
540
|
+
*/
|
|
541
|
+
cancelJob(jobId: string, reason: string): Promise<boolean>;
|
|
542
|
+
/**
|
|
543
|
+
* Bump `last_activity_at` to `now` (Unix ms) for the given jobs that are
|
|
544
|
+
* still in `processing`/`working` (internal-review worker heartbeat). Called on a
|
|
545
|
+
* tick by the `JobManager` for its locally-active jobs so a live worker's
|
|
546
|
+
* row stays fresh and the processing watchdog only fires when the process
|
|
547
|
+
* has actually died. A no-op for rows that have since gone terminal or
|
|
548
|
+
* `awaiting-input` (the SQL `WHERE` guards the status).
|
|
549
|
+
*/
|
|
550
|
+
heartbeatActiveJobs(jobIds: string[], now: number): Promise<void>;
|
|
551
|
+
/** Acquire the dedicated LISTEN connection. Call once at startup. */
|
|
552
|
+
initStreaming(): Promise<void>;
|
|
553
|
+
/** Release the dedicated LISTEN connection. Call on shutdown. */
|
|
554
|
+
shutdownStreaming(): Promise<void>;
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Message shape passed to `appendOutgoing` / `recordInbound`. The store
|
|
558
|
+
* allocates `seq` server-side inside the tx, so the caller doesn't supply it.
|
|
559
|
+
*/
|
|
560
|
+
interface OutgoingMessage {
|
|
561
|
+
from: "provider" | "requester";
|
|
562
|
+
type: MessageType;
|
|
563
|
+
timestamp: number;
|
|
564
|
+
content: Record<string, unknown> | object;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Runtime type guard for StreamableJobStore. Duck-typed, so it has to name every
|
|
568
|
+
* member the callers reach for — a store written against an earlier version of
|
|
569
|
+
* the interface would otherwise pass the guard and TypeError inside the route
|
|
570
|
+
* that calls the member it's missing.
|
|
571
|
+
*/
|
|
572
|
+
declare function isStreamableJobStore(store: JobStore): store is StreamableJobStore;
|
|
573
|
+
/**
|
|
574
|
+
* The stale-job reaper surface (internal-review) — the two methods a sweeper needs to
|
|
575
|
+
* find and atomically reap worker-stranded jobs. A strict subset of
|
|
576
|
+
* `StreamableJobStore`: `MemoryJobStore` and `PostgresJobStore` satisfy it via
|
|
577
|
+
* the full streamable interface, and the platform's `IsolateJobStore` (a plain
|
|
578
|
+
* single-machine `JobStore`, not streamable) implements just these two so the
|
|
579
|
+
* `IsolateJobManager` reaper can sweep the `isolate_jobs` table without taking
|
|
580
|
+
* on the streaming/LISTEN machinery it doesn't need.
|
|
581
|
+
*/
|
|
582
|
+
interface StaleJobReapable {
|
|
583
|
+
/** See {@link StreamableJobStore.findStaleJobs}. */
|
|
584
|
+
findStaleJobs(processingThresholdMs: number, awaitingThresholdMs: number, limit: number, capabilities: string[] | null): Promise<JobRecord[]>;
|
|
585
|
+
/** See {@link StreamableJobStore.cancelStaleJob}. */
|
|
586
|
+
cancelStaleJob(jobId: string, expectedActivityBefore: number, reason: string, terminalStatus: "failed" | "cancelled"): Promise<boolean>;
|
|
587
|
+
}
|
|
588
|
+
/** Runtime type guard for StaleJobReapable. */
|
|
589
|
+
declare function isStaleJobReapable(store: JobStore): store is JobStore & StaleJobReapable;
|
|
590
|
+
|
|
591
|
+
export { type AppendOutgoingOptions as A, type JobRecord as J, type OutgoingMessage as O, type PaymentCreditDelta as P, type ReceiptIssuingStore as R, type StaleJobReapable as S, type TopUpCapUnenforcedReason as T, type VerifyAndCreditResult as V, type JobStatus as a, type JobStore as b, type StreamableJobStore as c, type RequestIdClaim as d, type RequestIdClaimResult as e, type JobRetentionCursor as f, type JobCounters as g, isStreamableJobStore as h, isStaleJobReapable as i };
|